Introduction :

L’objectif de ce projet est de poursuivre les études que nous avons préalablement réalisées au cours du module 3 sur les données omiques issues de cet article :

Pavkovic, M., Pantano, L., Gerlach, C.V. et al. Multi omics analysis of fibrotic kidneys in two mouse models. Sci Data 6, 92 (2019). https://doi.org/10.1038/s41597-019-0095-5

Les auteurs étudient la fibrose rénale chez des souris. Ils utilisent deux modèles :

Les auteurs ont prélevé à différents temps après le traitement des échantillons de protéines et d’ARN de rein de souris.

Nous travaillerons sur le modèle FA (folic acid) en cherchant à mettre en relation les deux types de données omiques produites : transcriptomique et protéomique.

Je ne m’attarderai pas sur les analyses descriptives et exploratoires sur un seul type de données, car ces approches ont été déjà abordées lors des différents TP et le projet du module 3.

Note : il était difficile pour moi de me restreidre à deux figures pour certaines des analyses, puisque celles-ci servent souvent à guider mes choix et à les justifier. J’espère que vous ne m’en tiendrez pas rigueur…

setwd("/shared/projects/dubii2021/djarrige/m6-bioinfo-integr/mini-projet/")
mkdir -p data results
librairies = c("DESeq2",
               "dplyr",
               "VennDiagram",
               "tidyverse",
               "knitr",
               "WGCNA")

for (lib in librairies){
  if (!require(lib, character.only = TRUE)){
    install.packages(lib)
  }
  require(lib, character.only = TRUE)
}

required_Bioconductor <- c("mixOmics")

if (!requireNamespace("BiocManager", quietly = TRUE)){
  install.packages("BiocManager")
}

for (lib in required_Bioconductor) {
  if (!require(lib, character.only = TRUE)){
    BiocManager::install(lib)
  }
  require(lib, character.only = TRUE)
}

Préparation des données

# I download the raw data on the fa model and their corresponding metadata

url_base <- "https://github.com/DU-Bii/module-3-Stat-R/raw/master/stat-R_2021/data/pavkovic_2019/"
suffixes <- c("fa_raw_counts.tsv.gz",
              "fa_transcriptome_metadata.tsv",
              "pfa_model_counts.tsv.gz",
              "pfa_proteome_metadata.tsv")

for (suffix in suffixes){
  file_path <- paste0("data/", suffix)
  url <- paste0(url_base, suffix)
  if (!file.exists(file_path)){
    download.file(url, destfile = file_path)
  }
}
fa_raw <- read.csv("data/fa_raw_counts.tsv.gz", sep="\t", row.names=1)
pfa_raw <- read.csv("data/pfa_model_counts.tsv.gz", sep="\t")

fa_meta <- read.csv("data/fa_transcriptome_metadata.tsv", sep="\t", row.names=1)
pfa_meta <- read.csv("data/pfa_proteome_metadata.tsv", sep="\t", row.names=1)

Dans les données de protéomique, des id sont dupliqués. Pour pouvoir les utiliser comme row.names il va falloir les renommer.

id <- pfa_raw$id  # extracts the id 
id <- make.unique(id) # make them unique by appending numbers after duplicates
row.names(pfa_raw) <- id # use those unique id as rownames for the proteomics data
pfa_raw <- pfa_raw[, -1] # I take out the former id column

Les échantillons ne sont pas dans le même ordre dans les metadonnées transcriptomiques et les données. Je corrige cela !

ref_order <- paste0(rep(c("normal", "day1", "day2", "day3", "day7", "day14"), each=3), "_", 1:3)

fa_meta <- fa_meta[match(ref_order, fa_meta$sampleName),]
fa_raw <- fa_raw[, match(ref_order, colnames(fa_raw))]

Analyse d’expression différentielle :

Consignes :

Analyse d’expression différentielle pour les données de protéomique et transcriptomique => identifier les gènes/protéines significativement différentiellement exprimés dans le modèle FA en comparant Day 7 à Day 0.

Je vais utiliser DESeq2 pour traiter les données transcriptomiques. Avant le traitement par DESeq2 il faut :

-que les données de comptage soient brutes et entières (je vais considérer ici que les données dites raw des auteurs le sont bien réellement…)

-transformer le dataframe de comptage en matrice

Traitement des données de transcriptomique

fa_raw_matrix <- as.matrix(fa_raw)
fa_raw_matrix <- round(fa_raw_matrix)

fa_meta_simplified <- fa_meta[, c("sampleName", "condition")]

fa_deseq <- DESeqDataSetFromMatrix(countData = fa_raw_matrix, 
                                   colData = fa_meta,
                                   design = ~condition)
## converting counts to integer mode

Retrait des gènes pas ou quasi pas exprimés dans les données transcriptomiques, on ne garde que ceux dont le total de comptages est supérieur à 10.

# taking out low expression genes
fa_deseq <- fa_deseq[ rowSums(counts(fa_deseq)) > 10, ]

La fonction DESeq() réalise plusieurs étapes :

  • l’estimation des size factors, pour normaliser et prendre en compte la taille des libraries dans l’analyse

  • l’estimation de la dispersion pour chaque gène

  • un fit sur un modèle linéaire

Ensuite on s’interessera qu’aux conditions “normal” et “day7” en les extrayant des résultats.

Analyse différentielle DESeq2 transcriptomique

fa_deseq <- DESeq(fa_deseq)

# Extracting differential analyse results for conditions day7 on normal
res_DESeq <- results(fa_deseq, contrast = c("condition", "day7", "normal"))

# selecting genes differentially expressed with an adjusted (Benjamini-Hochberg) p value under 5%
significant_genes <- data.frame(id = row.names(res_DESeq[which(res_DESeq$padj < 0.05),]),
                                log2FoldChange_transcriptomics = res_DESeq[which(res_DESeq$padj < 0.05), "log2FoldChange"])

row.names(significant_genes) <- significant_genes$id

J’essaie DESeq2 également avec les données de protéomique, car celui-ci peut en théorie les traiter (une mention discrète dans la documentation) cependant il n’est pas construit pour les données de protéomique ce qui pourrait potentiellement biaiser le résultat. Nous allons essayer de voir cela.

Traitement des données de protéomique

pfa_raw_matrix <- as.matrix(pfa_raw)
pfa_raw_matrix <- round(pfa_raw_matrix)

pfa_deseq <- DESeqDataSetFromMatrix(countData = pfa_raw_matrix,
                                    colData = pfa_meta,
                                    design = ~ condition)
## converting counts to integer mode

Analyse différentielle DESeq2 protéomique :

pfa_deseq <- DESeq(pfa_deseq)
res_prot_DESeq2 <- results(pfa_deseq, contrast = c("condition", "day7", "normal"))

# Saving file for network analysis
tmp_df <- as.data.frame(res_prot_DESeq2)
tmp_df <- add_column(.data = tmp_df, gene_id = row.names(tmp_df), .before = 1)

write_csv(tmp_df, file = "results/protein_logFoldChange_7-0.csv")
remove(tmp_df)

significant_proteins <- data.frame(id = row.names(res_prot_DESeq2[which(res_prot_DESeq2$padj < 0.05), ]),
                                   log2FoldChange_proteomics = res_prot_DESeq2[which(res_prot_DESeq2$padj < 0.05), "log2FoldChange"])
row.names(significant_proteins) <-significant_proteins$id

Visualisation des résultats :

Les gènes ou protéines significativement (p value adjusted < 0.05%) différentiellement exprimés sont colorés en bleu.

par(mfrow=c(1,2))
plotMA(res_DESeq, las=1, cex.main = 1, ylim=c(-8,8),
       main = "Differentially expressed genes")
plotMA(res_prot_DESeq2, las=1, cex.main = 1, ylim=c(-8,8),
       main = "Differentially expressed proteins")
Differentially expressed genes and proteins.

Differentially expressed genes and proteins.

significant_all <- full_join(significant_genes, significant_proteins, by="id")
row.names(significant_all) <- significant_all$id

print(paste0("Differentially expressed RNAs: ", dim(significant_genes)[1]))
## [1] "Differentially expressed RNAs: 1996"
print(paste0("Differentially expressed proteins: ", dim(significant_proteins)[1]))
## [1] "Differentially expressed proteins: 4462"
print(paste0("Genes with both differentially expressed RNA and protein: ", dim(na.omit(significant_all))[1]))
## [1] "Genes with both differentially expressed RNA and protein: 841"
kable(na.omit(significant_all)[1:10,], caption = "Some examples:")
Some examples:
id log2FoldChange_transcriptomics log2FoldChange_proteomics
ENSMUSG00000000078 ENSMUSG00000000078 2.6721831 -1.0822107
ENSMUSG00000000154 ENSMUSG00000000154 -1.8348021 -2.3124483
ENSMUSG00000000171 ENSMUSG00000000171 -1.0890533 -1.0857821
ENSMUSG00000000563 ENSMUSG00000000563 -1.3407229 -1.1570848
ENSMUSG00000000673 ENSMUSG00000000673 -2.3409525 -2.1790929
ENSMUSG00000000876 ENSMUSG00000000876 -0.8951156 -1.4648656
ENSMUSG00000000915 ENSMUSG00000000915 1.6325695 0.2170728
ENSMUSG00000001025 ENSMUSG00000001025 2.4919169 1.6601354
ENSMUSG00000001089 ENSMUSG00000001089 2.3324185 0.5589445
ENSMUSG00000001100 ENSMUSG00000001100 -1.5391918 -0.8996146
strange_genes <-  c(na.omit(significant_all[which(significant_all$log2FoldChange_transcriptomics > 0 & significant_all$log2FoldChange_proteomics < 0), "id"]), na.omit(significant_all[which(significant_all$log2FoldChange_transcriptomics < 0 & significant_all$log2FoldChange_proteomics > 0), "id"]))


kable(significant_all[strange_genes, 2:3], caption = "Unconsistancy between RNA and protein")
Unconsistancy between RNA and protein
log2FoldChange_transcriptomics log2FoldChange_proteomics
ENSMUSG00000000078 2.6721831 -1.0822107
ENSMUSG00000001247 1.4046401 -0.2860428
ENSMUSG00000020744 2.7882125 -0.9110466
ENSMUSG00000024096 1.6492218 -0.3060593
ENSMUSG00000025742 1.1677440 -0.7607891
ENSMUSG00000040264 2.1397001 -0.7939093
ENSMUSG00000041797 1.5940262 -0.2789294
ENSMUSG00000002103 -1.7919769 0.8207154
ENSMUSG00000017210 -2.4336964 0.3247741
ENSMUSG00000020064 -1.0303276 0.1909243
ENSMUSG00000020180 -1.8407726 0.4060388
ENSMUSG00000021468 -1.4980361 0.2506465
ENSMUSG00000021660 -0.8394688 0.2133396
ENSMUSG00000021975 -1.4776663 0.4075119
ENSMUSG00000022205 -1.4609761 0.7303203
ENSMUSG00000028189 -1.4827506 0.4399862
ENSMUSG00000031660 -2.6095592 0.6029440
ENSMUSG00000031848 -1.8760340 0.5725588
ENSMUSG00000036181 -1.4033241 0.7846991
ENSMUSG00000042626 -1.9752141 0.4720450
ENSMUSG00000045996 -1.3126510 0.8334348
ENSMUSG00000051695 -1.1590639 0.3449563
ENSMUSG00000059796 -1.7387242 0.6464077
ENSMUSG00000060639 -1.7564435 0.7010975
ENSMUSG00000061048 -2.8027373 2.2505599
print(paste0("Unconsistant genes: ", (length(strange_genes) / nrow(significant_all) * 100), " %"))
## [1] "Unconsistant genes: 0.445077443475165 %"

Le diagramme de Venn ci-dessous présente l’intersection entre les gènes différentiellement exprimés au niveau ARN et ceux au niveau protéine, entre la condition normale et day7. Environ 840 gènes voient leur niveau significativement différent pour les deux types de molécule.

On pourrait ensuite par exemple réaliser une analyse d’enrichissement fonctionnel sur cette liste.

Attention cependant, comme on peut le constater dans les exemples ci-dessus quelques gènes peuvent être surexprimés en ARN et sous exprimés en protéines (ou inversement) ! Heureusement ces gènes aux expressions contradictoires sont rares ! Difficile de dire sans informations complémentaires s’il pourrait s’agir d’une réalité biologique (par exemple la protéine pourrait être soumise à une dégradation très intense et le rein pourrait tenter de compenser en produisant beaucoup d’ARNm), d’un biais technique, ou bien si l’utilisation de DESeq2 sur des données de protéomique pourrait entrainer des artéfacts… Beaucoup de protéines sont jugées différentiellement exprimées par DESeq2 en comparaison aux ARN.

area1 <- dim(significant_proteins)[1]
area2 <- dim(significant_genes)[1]
cross <- dim(na.omit(significant_all))[1]

venn.plot <- draw.pairwise.venn(area1 = area1, 
                                area2 = area2, 
                                cross.area = cross,
                                fontfamily = "sans",
                                category = c("significant proteomics", "significant transcriptomics"),
                                fill = c(alpha("magenta",0.3), alpha('cyan',0.3)),
                                lwd=0,
                                cat.col = c("magenta", "cyan"),
                                cat.pos = c(0, 0),
                                cat.dist = c(0.02, 0.087),
                                cat.fontfamily = "sans")
grid.draw(venn.plot)
Differentially expressed genes and proteins, overlap.

Differentially expressed genes and proteins, overlap.

grid.newpage()

Analyse multi-omique

Consignes :

Analyse multi-omique (transcripto + protéo) avec, au choix, MOFA, mixOmics, mixKernel ou d’autres outils de factorisation multi-matrices. Vous pouvez soit vous focaliser sur un time point, soit intégrer les différents time points, en partant des données normalisées fournies dans le matériel supplémentaire du papier.

J’utilise mixOmics. Cependant je vais essayer de normaliser les données moi-même (en m’inspirant de celles utilisées dans le module 3) et de regarder tous les timempoints possibles (à part le jour 3 qui est absent des données de protéomiques).

Avant d’explorer nos données avec mixOmics il faut :

  • les normaliser

  • les filtrer (pour avoir maximum environ 10 000 gènes ou protéines)

  • passer les échantillons en lignes et les gènes/protéines en colonnes

  • avoir les mêmes échantillons dans les deux analyses

Pour les données de protéomique, on a un nombre d’observables raisonnable (8044), je ne filtrerai donc pas de protéines.

En revanche pour les données transcriptomiques, on a beaucoup d’ARNs (46679), il vaut mieux les filtrer !

Traitement des données

Retrait des gènes pas ou quasi pas exprimés dans les données transcriptomiques, on ne garde que ceux dont la somme des comptage est supérieure à 10.

fa_filtered <- fa_raw[which(rowSums(fa_raw) > 10), ]

Normalisation inter-échantillon sur les troisièmes quartiles

third_quantiles <- apply(fa_filtered, 2 , quantile, p=0.75, na.rm = TRUE)
global_third_quantile <- quantile(unlist(fa_filtered), p=0.75)
size_factors <- 1 / third_quantiles * global_third_quantile


fa_norm <- t(t(fa_filtered) * size_factors)

Transformation en log2

fa_log2 <- log2(fa_norm + 1)

#boxplot(fa_log2, col=fa_meta$color)

Calculs de statistiques sur les gènes pour ne garder que les gènes les plus exprimés dans l’analyse.

gene_stats <- data.frame(row.names = row.names(fa_log2))
gene_stats$mean <- apply(fa_log2, 1, mean)
gene_stats$var <- apply(fa_log2, 1, var)

Selection des gènes exprimés les plus variables.

gene_kept <- gene_stats[which((gene_stats$mean > 4) & (gene_stats$var > 1.5)), ]
fa_log2 <- fa_log2[row.names(gene_kept),]

#boxplot(fa_log2, col=fa_meta$color)

Normalisation inter-échantillons des données de protéomique

third_quantiles <- apply(pfa_raw, 2 , quantile, p=0.75, na.rm = TRUE)
global_third_quantile <- quantile(unlist(pfa_raw), p=0.75)
size_factors <- 1 / third_quantiles * global_third_quantile


pfa_norm <- t(t(pfa_raw) * size_factors)

Transformation des données de protéomique en log2

pfa_log2 <- log2(pfa_norm + 1)

#boxplot(pfa_log2, col=pfa_meta$color)

Je vais retirer les réplicats biologique n°3, afin d’avoir les même individus dans les deux jeux de données. Je retire également les timepoints du jour 3, qui sont absents du jeu protéomique.

fa_mix <- fa_log2[, -c(3, 6, 9, 10, 11, 12, 15, 18)]
meta_mix <- fa_meta[-c(3, 6, 9, 10, 11, 12, 15, 18),-1]

multi_dataset <- list(genes = t(fa_mix),
                      proteins = t(pfa_log2),
                      conditions = meta_mix)

Sparse Partial to Least Squares (sPLS)

Cette méthode est non-supervisée, les échantillons ne sont pas séparés en groupes. La sparse PLS va intégrer les données de transcriptomique et de protéomique tout en sélectionnant les variables (gènes ou protéines) les plus covariants ou anti-covariants dans les deux jeux de données omiques.

mix_sPLS <- spls(X = multi_dataset$genes,
                 Y = multi_dataset$proteins,
                 keepX= c(25, 25),
                 keepY= c(25, 25))

plotIndiv(mix_sPLS, rep.space = 'XY-variate', cex=3.5,
          group = multi_dataset$conditions$condition,
          title = "Individuals Sparse PLS")
sPLS on transcriptomic and proteomic datasets

sPLS on transcriptomic and proteomic datasets

Ici, je demande à la méthode de garder 25 variables de chaque jeu de données par composante. Je demande d’inclure deux composantes dans le modèle.

Le plot présente une synthèse de l’analyse des deux jeux de données.

On constate que la sPLS nous permet non seulement d’étudier la relation entre nos deux jeux de données, mais ici permet de séparer “naturellement” (sans supervision) les échantillons en “clusters” distincts. Les normaux isolés, les conditions peu après l’injection proches et celles plus tardives ensemble.

par(mfrow=c(1,2))
plotLoadings(mix_sPLS, size.title=1.2, comp=1, 
             title = "Loadings on comp 1",
             subtitle = c("RNAs", "Proteins"),
             size.subtitle = 1)
sPLS: Loadings on genes and proteins.

sPLS: Loadings on genes and proteins.

plotLoadings(mix_sPLS, size.title=1.2, comp=2,
             title = "Loadings on comp 2",
             subtitle = c("RNAs", "Proteins"),
             size.subtitle = 1)
sPLS: Loadings on genes and proteins.

sPLS: Loadings on genes and proteins.

Ici les loading représentent quels gènes contribuent le plus à la covariance entre les deux jeux de données omiques. Le bloc X représente les gènes dont l’expression caractérise la relation entre données transcriptomiques et protéomiques, le bloc Y les protéines.

On obtient donc des listes de gènes et protéines qui pourront caractériser les échantillons.

Réseau de co-expression avec WGCNA

Consignes :

Construction de réseau avec WGCNA à partir des données transcriptomiques.

Reconstruct the co-expression network from all the time points of the FA transcriptomics data. Propose to filter and remove all the zero expressed genes, the NAs and the less informative genes from the transcriptomics data. (I remove all the genes that are not expressed in at least 9 out of the 18 conditions (expression > 1 TPM in 9) and then filter with the coefficient of variation > 0.75).

Then apply the first part of the network reconstruction steps as we saw them on the WGCNA course until the module predictions.

Instead of using WGCNA’s module prediction routines, apply a universal threshold of 0.5 on the adjacency matrix, and obtain an adjacency matrix that is reduced in size. This is the network. Import it to Cytoscape with aMatReader plugin. Visualize, analyze the network and superimpose the proteomics data on it.

Filtrage des données de transcriptomique

# Computing gene statistics
stats_wgcna <- data.frame(mean = apply(fa_raw, 1, mean),
                          sd = apply(fa_raw, 1, sd),
                          row.names = row.names(fa_raw))

stats_wgcna$coef_var <- (stats_wgcna$sd / stats_wgcna$mean)
stats_wgcna$null = apply(fa_raw == 0, 1, sum, na.rm = TRUE)


# filtering at least expressed in 9 samples and coefficient of variation superior to 0.75

fa_wgcna <- fa_raw[ row.names(stats_wgcna[ which(stats_wgcna$null < 9 & stats_wgcna$coef_var > 0.75), ]), ]

Je réalise aussi une normalisation et une transformation log2, car sinon le clustering est très différent de celui obtenu lors du mini-projet du module 3.

third_quantiles <- apply(fa_wgcna, 2 , quantile, p=0.75, na.rm = TRUE)
global_third_quantile <- quantile(unlist(fa_wgcna), p=0.75)
size_factors <- 1 / third_quantiles * global_third_quantile

fa_wgcna <- t(t(fa_wgcna) * size_factors)
fa_wgcna <- log2(fa_wgcna + 1)
sample_tree = hclust(dist(t(fa_wgcna)), method = "complete")

plot(sample_tree, main = "Sample clustering to detect outliers", 
     xlab="", cex.main = 1)

abline(h = 250, col = "red")

# For me day2_2 is an outlier, I take it out.
fa_wgcna <- fa_wgcna[,-8]

sample_tree2 <- hclust(dist(t(fa_wgcna)), method = "complete")

Je retire l’échantillon day2_2 qui est un outlier

Maintenant je crée un data.frame avec des données binaires, reflétant le temps de prélèvement.

traits_data <- data.frame(names = fa_meta$sampleName)

traits_data$normal <- as.integer(startsWith(traits_data$names, "normal"))
traits_data$day1 <- as.integer(startsWith(traits_data$names, "day1_"))
traits_data$day2 <- as.integer(startsWith(traits_data$names, "day2"))
traits_data$day3 <- as.integer(startsWith(traits_data$names, "day3"))
traits_data$day7 <- as.integer(startsWith(traits_data$names, "day7"))
traits_data$day14 <- as.integer(startsWith(traits_data$names, "day14"))

row.names(traits_data) <- traits_data$names
traits_data <- traits_data[,-1]
traits_data <- traits_data[-8,]

Traits et échantillons

Les différents temps de prélèvement corrèlent plutôt bien avec ce qui est attendu pour les échantillons.

# Convert traits to a color representation: red means yes
traits_colors = numbers2colors(traits_data, signed = FALSE);

# Plot the sample dendrogram and the colors underneath.
plotDendroAndColors(sample_tree2, traits_colors,
                    groupLabels = names(traits_data),
                    main = "Sample dendrogram and traits heatmap")

Je transpose la matrice de données d’expression pour la suite de l’analyse.

fa_wgcna <- t(fa_wgcna)

WGCNA construction du réseau

Recherche d’une puissance adaptée

save(fa_wgcna, traits_data, file = "results/fa_transcriptomics_wgcna.RData")

allowWGCNAThreads()
## Allowing multi-threading with up to 56 threads.
# Load the data saved in the first part
lnames = load(file = "results/fa_transcriptomics_wgcna.RData");


# Choose a set of soft-thresholding powers
powers = c(c(1:10), seq(from = 12, to = 20, by = 2))

# Call the network topology analysis function
sft = pickSoftThreshold(fa_wgcna, powerVector = powers, verbose = 0)
##    Power SFT.R.sq slope truncated.R.sq  mean.k. median.k. max.k.
## 1      1    0.180 -3.44          0.683 2580.000  2530.000 3480.0
## 2      2    0.765 -4.05          0.900  849.000   799.000 1560.0
## 3      3    0.924 -3.68          0.965  346.000   310.000  862.0
## 4      4    0.955 -3.34          0.971  163.000   138.000  547.0
## 5      5    0.956 -3.07          0.967   84.600    68.000  380.0
## 6      6    0.963 -2.81          0.969   47.600    35.900  281.0
## 7      7    0.965 -2.62          0.972   28.600    20.100  217.0
## 8      8    0.956 -2.48          0.963   18.000    11.800  174.0
## 9      9    0.952 -2.37          0.962   11.900     7.220  143.0
## 10    10    0.957 -2.24          0.968    8.150     4.560  120.0
## 11    12    0.960 -2.05          0.976    4.190     1.980   87.8
## 12    14    0.945 -1.95          0.969    2.380     0.939   67.4
## 13    16    0.933 -1.86          0.969    1.460     0.478   53.4
## 14    18    0.934 -1.78          0.974    0.954     0.258   43.3
## 15    20    0.933 -1.70          0.972    0.655     0.146   35.7
# Plot the results:
par(mfrow = c(1, 2));
options(repr.plot.width = 14, repr.plot.height = 10);

# Scale-free topology fit index as a function of the soft-thresholding power
plot(sft$fitIndices[,1], -sign(sft$fitIndices[,3])*sft$fitIndices[,2],
     xlab = "Soft Threshold (power)", ylab = "Scale Free Topology Model Fit,signed R^2", type = "n",
     main = paste("Scale independence"));
text(sft$fitIndices[,1], -sign(sft$fitIndices[,3])*sft$fitIndices[,2],
     labels = powers, cex = 0.9, col = "red");
# this line corresponds to using an R^2 cut-off of h
abline(h = 0.90, col = "red")

# Mean connectivity as a function of the soft-thresholding power
plot(sft$fitIndices[,1], sft$fitIndices[,5],
     xlab = "Soft Threshold (power)", ylab = "Mean Connectivity", type = "n",
     main = paste("Mean connectivity"))
text(sft$fitIndices[,1], sft$fitIndices[,5], labels = powers, cex = 0.9, col = "red")
Soft thresholding

Soft thresholding

Je prends le power 6 pour la suite du pipeline.

Prédiction de modules

net = blockwiseModules(fa_wgcna, power = 6,
                       TOMType = "signed", minModuleSize = 30,
                       reassignThreshold = 1e-6, mergeCutHeight = 0.25,
                       numericLabels = TRUE, pamRespectsDendro = FALSE,
                       saveTOMs = TRUE, nThreads = 8,
                       saveTOMFileBase = "results/fa_transcriptomic_TOM",
                       verbose = 0)

# Convert labels to colors for plotting
mergedColors = labels2colors(net$colors)
#mergedColors
# Plot the dendrogram and the module colors underneath
plotDendroAndColors(net$dendrograms[[1]], mergedColors[net$blockGenes[[1]]],
                    "Module colors",
                    dendroLabels = FALSE, hang = 0.03,
                    addGuide = TRUE, guideHang = 0.05)
Dendrogram and modules.

Dendrogram and modules.

moduleLabels = net$colors
moduleColors = labels2colors(net$colors)
MEs = net$MEs;
geneTree = net$dendrograms[[1]];
save(MEs, moduleLabels, moduleColors, geneTree,
     file = "results/fa_transcriptomic_networkConstruction-auto.RData")



lnames = load(file = "results/fa_transcriptomics_wgcna.RData");

# Load network data saved in the second part.
lnames = load(file = "results/fa_transcriptomic_networkConstruction-auto.RData");
# Define numbers of genes and samples
nGenes = ncol(fa_wgcna);
nSamples = nrow(fa_wgcna);

# Recalculate MEs with color labels
MEs0 = moduleEigengenes(fa_wgcna, moduleColors)$eigengenes
MEs = orderMEs(MEs0)
moduleTraitCor = cor(MEs, traits_data, use = "p");
moduleTraitPvalue = corPvalueStudent(moduleTraitCor, nSamples);

options(repr.plot.width=16, repr.plot.height=12)
# Will display correlations and their p-values
textMatrix =  paste(signif(moduleTraitCor, 2), "\n(",
                           signif(moduleTraitPvalue, 1), ")", sep = "");
dim(textMatrix) = dim(moduleTraitCor)
par(mar = c(6, 11, 1, 0));
# Display the correlation values within a heatmap plot
labeledHeatmap(Matrix = moduleTraitCor,
               xLabels = names(traits_data),
               yLabels = names(MEs),
               ySymbols = names(MEs),
               colorLabels = FALSE,
               colors = blueWhiteRed(50),
               textMatrix = textMatrix,
               setStdMargins = FALSE,
               cex.text = 0.5,
               cex.legendLabel = 0.6,
               zlim = c(-1,1),
               main = paste("Module-trait relationships"))
Module-trait relationships

Module-trait relationships

J’ai tenté de comparer l’utilisation du TOM sans prédiction de module, représentant l’ensemble des gènes, avec le modTOM qui ne contient qu’une sélection de deux modules d’intérêt.

J’ai rencontré des difficultés avec l’utilisation du threshold, mes données ne dépassant jamais le niveau demandé de 0.5 (voir histogrammes) malgré l’essai de nombreux paramètres, puissances et méthodes.

J’ai alors supposé que ce threshold était basé sur l’adjacency et pas directement la corrélation, et en ai calculé un en suivant la formule de calcul de la méthode “signed” sur une corrélation de 0.5 et un power de 6, soit environ 0.18.

# Recalculate topological overlap if needed
TOM = TOMsimilarityFromExpr(fa_wgcna, power = 6,
                            networkType = "signed",
                            verbose = 0)

# Select modules
# I pick one module strongly expressed in normal samples and one in day 7 samples
modules = c("yellow", "white");  

# Select module probes
probes = colnames(fa_wgcna)
inModule = is.finite(match(moduleColors, modules))
modProbes = probes[inModule]

# Select the corresponding Topological Overlap
modTOM = TOM[inModule, inModule];
dimnames(modTOM) = list(modProbes, modProbes)

####
#Adjacency values visualization to decide on threshold
par(mfrow=c(1,2))
hist(unlist(TOM), breaks = 100, cex.main = 0.8)
hist(unlist(modTOM), breaks = 100, cex.main = 0.8)

# threshold : "adjacency threshold for including edges in the output." (from the doc)
# With "signed" type adjacency -> adjacency = (0.5 * (1+cor) )^power
# if correlation = 0.5 -> we get adjacency = 0.18

Exportation des données pour Cytoscape

J’essaye de produire un sous réseau avec la sélection de deux modules puis le réseau complet.

# Export the network into edge and node list files Cytoscape can read
### Here I keep the whole network
cyt = exportNetworkToCytoscape(TOM,
  edgeFile = paste("results/CytoscapeInput-edges", ".txt", sep=""),
  nodeFile = paste("results/CytoscapeInput-nodes", ".txt", sep=""),
  weighted = TRUE,
  threshold = 0.18, # I modified the threshold here
  nodeNames = probes,
  #nodeAttr = moduleColors[inModule]
  )

# Export the network into edge and node list files Cytoscape can read
### Here I export only two gene modules of interest
cyt = exportNetworkToCytoscape(modTOM,
  edgeFile = paste("results/CytoscapeInput-edges-", paste(modules, collapse="-"), ".txt", sep=""),
  nodeFile = paste("results/CytoscapeInput-nodes-", paste(modules, collapse="-"), ".txt", sep=""),
  weighted = TRUE,
  threshold = 0.18, # I modified the threshold here
  nodeNames = modProbes,
  nodeAttr = moduleColors[inModule]
  )

Visualisation du réseau dans Cytoscape

Consigne :

Colorez dans le réseau choisi les noeuds en fonction des données de protéomiques avec un gradient de couleur correspondant au fold-change des données de protéomique.

Sous-réseau (2 modules)

Visualisons d’abord le sous-réseau avec les deux modules sélectionnés.

Les gènes se séparent en deux réseaux. J’ai coloré les nodes en fonction de leur appartenance au module yellow (plus exprimé en condition normale) ou white (plus exprimé en condition day7). Etrangement je n’observe pas de séparation évidente des gènes en fonction de leur appartenance aux modules.

reseau_2_modules

Réseau complet

Maintenant, visualisons le réseau complet des gènes variables :

Dans cette version brute on retrouve la séparation en deux réseaux isolés. De plus on constate que celui de gauche semble avoir deux parties moins fortement correlées.

reseau_entier

Puis, je n’ai conservé que les gènes pour lesquels une information protéique était disponible. Ensuite, j’ai coloré les nodes en fonction du log fold change entre les conditions normales et jour7.

On constate ici une séparation franche des gènes plus exprimés en condition day7 qu’en condition normal (en rouge) de ceux moins exprimés qu’en condition normale (en bleu).

Le réseau de droite regroupe des gènes plus exprimés lors de la nephropathie. Celui de gauche est intéressant ; il contient un “pôle” de gènes moins exprimés en condition day7 qu’en normal (reflétant certainement l’activité normale d’un rein sain) et un petit “pôle” de gènes à l’inverse plus exprimés.

Nos données protéomiques donnent une cohérence supplémentaire à ce réseau inféré avec WGCNA. Aussi, je pense que l’utilisation de DESeq2 pour les données de spectrométrie de masse n’est peut-être pas idéale, mais donne un résulat final globalement cohérent avec les données de RNA-seq.

reseau_entier_prot

sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-conda-linux-gnu (64-bit)
## Running under: CentOS Linux 7 (Core)
## 
## Matrix products: default
## BLAS/LAPACK: /shared/ifbstor1/software/miniconda/envs/r-4.0.3/lib/libopenblasp-r0.3.10.so
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
## 
## attached base packages:
##  [1] grid      parallel  stats4    stats     graphics  grDevices utils    
##  [8] datasets  methods   base     
## 
## other attached packages:
##  [1] mixOmics_6.14.0             lattice_0.20-41            
##  [3] MASS_7.3-53.1               WGCNA_1.70-3               
##  [5] fastcluster_1.1.25          dynamicTreeCut_1.63-1      
##  [7] knitr_1.33                  forcats_0.5.1              
##  [9] stringr_1.4.0               purrr_0.3.4                
## [11] readr_1.4.0                 tidyr_1.1.3                
## [13] tibble_3.1.2                ggplot2_3.3.3              
## [15] tidyverse_1.3.0             VennDiagram_1.6.20         
## [17] futile.logger_1.4.3         dplyr_1.0.6                
## [19] DESeq2_1.30.1               SummarizedExperiment_1.20.0
## [21] Biobase_2.50.0              MatrixGenerics_1.2.1       
## [23] matrixStats_0.58.0          GenomicRanges_1.42.0       
## [25] GenomeInfoDb_1.26.7         IRanges_2.24.1             
## [27] S4Vectors_0.28.1            BiocGenerics_0.36.1        
## 
## loaded via a namespace (and not attached):
##   [1] readxl_1.3.1           backports_1.2.1        Hmisc_4.5-0           
##   [4] plyr_1.8.6             igraph_1.2.6           splines_4.0.3         
##   [7] BiocParallel_1.24.1    digest_0.6.27          foreach_1.5.1         
##  [10] htmltools_0.5.1.1      GO.db_3.12.1           fansi_0.4.2           
##  [13] magrittr_2.0.1         checkmate_2.0.0        memoise_2.0.0         
##  [16] cluster_2.1.1          doParallel_1.0.16      annotate_1.68.0       
##  [19] modelr_0.1.8           rARPACK_0.11-0         jpeg_0.1-8.1          
##  [22] colorspace_2.0-1       blob_1.2.1             rvest_1.0.0           
##  [25] ggrepel_0.9.1          haven_2.3.1            xfun_0.23             
##  [28] crayon_1.4.1           RCurl_1.98-1.3         jsonlite_1.7.2        
##  [31] genefilter_1.72.1      impute_1.64.0          survival_3.2-10       
##  [34] iterators_1.0.13       glue_1.4.2             gtable_0.3.0          
##  [37] zlibbioc_1.36.0        XVector_0.30.0         DelayedArray_0.16.3   
##  [40] scales_1.1.1           futile.options_1.0.1   DBI_1.1.1             
##  [43] Rcpp_1.0.6             xtable_1.8-4           htmlTable_2.2.1       
##  [46] foreign_0.8-81         bit_4.0.4              preprocessCore_1.52.1 
##  [49] Formula_1.2-4          htmlwidgets_1.5.3      httr_1.4.2            
##  [52] RColorBrewer_1.1-2     ellipsis_0.3.2         farver_2.1.0          
##  [55] pkgconfig_2.0.3        XML_3.99-0.6           nnet_7.3-15           
##  [58] sass_0.4.0             dbplyr_2.1.1           locfit_1.5-9.4        
##  [61] utf8_1.2.1             labeling_0.4.2         tidyselect_1.1.1      
##  [64] rlang_0.4.11           reshape2_1.4.4         AnnotationDbi_1.52.0  
##  [67] munsell_0.5.0          cellranger_1.1.0       tools_4.0.3           
##  [70] cachem_1.0.5           cli_2.5.0              generics_0.1.0        
##  [73] RSQLite_2.2.7          broom_0.7.5            evaluate_0.14         
##  [76] fastmap_1.1.0          yaml_2.2.1             bit64_4.0.5           
##  [79] fs_1.5.0               formatR_1.9            xml2_1.3.2            
##  [82] compiler_4.0.3         rstudioapi_0.13        png_0.1-7             
##  [85] reprex_1.0.0           geneplotter_1.68.0     bslib_0.2.5.1         
##  [88] stringi_1.6.2          highr_0.9              RSpectra_0.16-0       
##  [91] Matrix_1.3-2           vctrs_0.3.8            pillar_1.6.1          
##  [94] lifecycle_1.0.0        BiocManager_1.30.10    jquerylib_0.1.4       
##  [97] data.table_1.14.0      bitops_1.0-7           corpcor_1.6.9         
## [100] R6_2.5.0               latticeExtra_0.6-29    gridExtra_2.3         
## [103] codetools_0.2-18       lambda.r_1.2.4         assertthat_0.2.1      
## [106] withr_2.4.2            GenomeInfoDbData_1.2.4 hms_1.1.0             
## [109] rpart_4.1-15           rmarkdown_2.8          lubridate_1.7.10      
## [112] base64enc_0.1-3        ellipse_0.4.2
LS0tCnRpdGxlOiAiSkFSUklHRS1ET01JVElMTEVfZXZhbHVhdGlvbi1tNi0yMDIxIgphdXRob3I6ICJEb21pdGlsbGUgSmFycmlnZSIKZGF0ZTogJ2ByIFN5cy5EYXRlKClgJwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHNlbGZfY29udGFpbmVkOiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIGhpZ2hsaWdodDogemVuYnVybgogICAgdGhlbWU6IHlldGkKICAgIHRvYzogeWVzCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogIHBkZl9kb2N1bWVudDoKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIGhpZ2hsaWdodDogemVuYnVybgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogMwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBldmFsID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0cyA9IFRSVUUpCmBgYAoKIyBJbnRyb2R1Y3Rpb24gOgoKCkwnb2JqZWN0aWYgZGUgY2UgcHJvamV0IGVzdCBkZSBwb3Vyc3VpdnJlIGxlcyDDqXR1ZGVzIHF1ZSBub3VzIGF2b25zIHByw6lhbGFibGVtZW50IHLDqWFsaXPDqWVzIGF1IGNvdXJzIGR1IG1vZHVsZSAzIHN1ciBsZXMgZG9ubsOpZXMgb21pcXVlcyBpc3N1ZXMgZGUgY2V0IGFydGljbGUgOgoKKipQYXZrb3ZpYywgTS4sIFBhbnRhbm8sIEwuLCBHZXJsYWNoLCBDLlYuIGV0IGFsLiBNdWx0aSBvbWljcyBhbmFseXNpcyBvZiBmaWJyb3RpYyBraWRuZXlzIGluIHR3byBtb3VzZSBtb2RlbHMuIFNjaSBEYXRhIDYsIDkyICgyMDE5KS4gaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTk3LTAxOS0wMDk1LTUqKgoKTGVzIGF1dGV1cnMgw6l0dWRpZW50IGxhIGZpYnJvc2UgcsOpbmFsZSBjaGV6IGRlcyBzb3VyaXMuIElscyB1dGlsaXNlbnQgZGV1eCBtb2TDqGxlcyA6CgotIHVuIG/DuSBsYSBuZXBocm9wYXRoaWUgZXN0IGluZHVpdGUgcGFyIHVuIHRyYWl0ZW1lbnQgY2hpcnVyZ2ljYWwsIGlycmV2ZXJzaWJsZSAKCi0gdW4gdHJhaXRlbWVudCDDoCBsJ2FjaWRlIGZvbGlxdWUgcXVpIGVzdCByZXZlcnNpYmxlLgoKCkxlcyBhdXRldXJzIG9udCBwcsOpbGV2w6kgw6AgZGlmZsOpcmVudHMgdGVtcHMgYXByw6hzIGxlIHRyYWl0ZW1lbnQgZGVzIMOpY2hhbnRpbGxvbnMgZGUgcHJvdMOpaW5lcyBldCBkJ0FSTiBkZSByZWluIGRlIHNvdXJpcy4gCgpOb3VzIHRyYXZhaWxsZXJvbnMgc3VyIGxlIG1vZMOobGUgRkEgKGZvbGljIGFjaWQpIGVuIGNoZXJjaGFudCDDoCBtZXR0cmUgZW4gcmVsYXRpb24gbGVzIGRldXggdHlwZXMgZGUgZG9ubsOpZXMgb21pcXVlcyBwcm9kdWl0ZXMgOiB0cmFuc2NyaXB0b21pcXVlIGV0IHByb3TDqW9taXF1ZS4KCkplIG5lIG0nYXR0YXJkZXJhaSBwYXMgc3VyIGxlcyBhbmFseXNlcyBkZXNjcmlwdGl2ZXMgZXQgZXhwbG9yYXRvaXJlcyBzdXIgdW4gc2V1bCB0eXBlIGRlIGRvbm7DqWVzLCBjYXIgY2VzIGFwcHJvY2hlcyBvbnQgw6l0w6kgZMOpasOgIGFib3Jkw6llcyBsb3JzIGRlcyBkaWZmw6lyZW50cyBUUCBldCBsZSBwcm9qZXQgZHUgbW9kdWxlIDMuCgoKX05vdGUgOiBpbCDDqXRhaXQgZGlmZmljaWxlIHBvdXIgbW9pIGRlIG1lIHJlc3RyZWlkcmUgw6AgZGV1eCBmaWd1cmVzIHBvdXIgY2VydGFpbmVzIGRlcyBhbmFseXNlcywgcHVpc3F1ZSBjZWxsZXMtY2kgc2VydmVudCBzb3V2ZW50IMOgIGd1aWRlciBtZXMgY2hvaXggZXQgw6AgbGVzIGp1c3RpZmllci4gSidlc3DDqHJlIHF1ZSB2b3VzIG5lIG0nZW4gdGllbmRyZXogcGFzIHJpZ3VldXIuLi5fCgoKCgpgYGB7ciBzZXRfd29ya2luZ19kaXJlY3Rvcnl9CnNldHdkKCIvc2hhcmVkL3Byb2plY3RzL2R1YmlpMjAyMS9kamFycmlnZS9tNi1iaW9pbmZvLWludGVnci9taW5pLXByb2pldC8iKQpgYGAKCmBgYHtiYXNoIG1ha2luZ19zdWJkaXJlY3Rvcmllc30KbWtkaXIgLXAgZGF0YSByZXN1bHRzCmBgYAoKYGBge3IgbmVjZXNzYXJ5X2xpYnJhaXJpZXMsIG1lc3NhZ2U9RkFMU0V9CgpsaWJyYWlyaWVzID0gYygiREVTZXEyIiwKICAgICAgICAgICAgICAgImRwbHlyIiwKICAgICAgICAgICAgICAgIlZlbm5EaWFncmFtIiwKICAgICAgICAgICAgICAgInRpZHl2ZXJzZSIsCiAgICAgICAgICAgICAgICJrbml0ciIsCiAgICAgICAgICAgICAgICJXR0NOQSIpCgpmb3IgKGxpYiBpbiBsaWJyYWlyaWVzKXsKICBpZiAoIXJlcXVpcmUobGliLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKXsKICAgIGluc3RhbGwucGFja2FnZXMobGliKQogIH0KICByZXF1aXJlKGxpYiwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQp9CgpyZXF1aXJlZF9CaW9jb25kdWN0b3IgPC0gYygibWl4T21pY3MiKQoKaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSl7CiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQp9Cgpmb3IgKGxpYiBpbiByZXF1aXJlZF9CaW9jb25kdWN0b3IpIHsKICBpZiAoIXJlcXVpcmUobGliLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKXsKICAgIEJpb2NNYW5hZ2VyOjppbnN0YWxsKGxpYikKICB9CiAgcmVxdWlyZShsaWIsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkKfQoKCmBgYAoKCiMgUHLDqXBhcmF0aW9uIGRlcyBkb25uw6llcwoKYGBge3IgZG93bmxvYWRpbmdfZGF0YV9vbmNlfQoKIyBJIGRvd25sb2FkIHRoZSByYXcgZGF0YSBvbiB0aGUgZmEgbW9kZWwgYW5kIHRoZWlyIGNvcnJlc3BvbmRpbmcgbWV0YWRhdGEKCnVybF9iYXNlIDwtICJodHRwczovL2dpdGh1Yi5jb20vRFUtQmlpL21vZHVsZS0zLVN0YXQtUi9yYXcvbWFzdGVyL3N0YXQtUl8yMDIxL2RhdGEvcGF2a292aWNfMjAxOS8iCnN1ZmZpeGVzIDwtIGMoImZhX3Jhd19jb3VudHMudHN2Lmd6IiwKICAgICAgICAgICAgICAiZmFfdHJhbnNjcmlwdG9tZV9tZXRhZGF0YS50c3YiLAogICAgICAgICAgICAgICJwZmFfbW9kZWxfY291bnRzLnRzdi5neiIsCiAgICAgICAgICAgICAgInBmYV9wcm90ZW9tZV9tZXRhZGF0YS50c3YiKQoKZm9yIChzdWZmaXggaW4gc3VmZml4ZXMpewogIGZpbGVfcGF0aCA8LSBwYXN0ZTAoImRhdGEvIiwgc3VmZml4KQogIHVybCA8LSBwYXN0ZTAodXJsX2Jhc2UsIHN1ZmZpeCkKICBpZiAoIWZpbGUuZXhpc3RzKGZpbGVfcGF0aCkpewogICAgZG93bmxvYWQuZmlsZSh1cmwsIGRlc3RmaWxlID0gZmlsZV9wYXRoKQogIH0KfQoKYGBgCgoKYGBge3IgbG9hZGluZ19kYXRhfQoKZmFfcmF3IDwtIHJlYWQuY3N2KCJkYXRhL2ZhX3Jhd19jb3VudHMudHN2Lmd6Iiwgc2VwPSJcdCIsIHJvdy5uYW1lcz0xKQpwZmFfcmF3IDwtIHJlYWQuY3N2KCJkYXRhL3BmYV9tb2RlbF9jb3VudHMudHN2Lmd6Iiwgc2VwPSJcdCIpCgpmYV9tZXRhIDwtIHJlYWQuY3N2KCJkYXRhL2ZhX3RyYW5zY3JpcHRvbWVfbWV0YWRhdGEudHN2Iiwgc2VwPSJcdCIsIHJvdy5uYW1lcz0xKQpwZmFfbWV0YSA8LSByZWFkLmNzdigiZGF0YS9wZmFfcHJvdGVvbWVfbWV0YWRhdGEudHN2Iiwgc2VwPSJcdCIsIHJvdy5uYW1lcz0xKQoKYGBgCgpEYW5zIGxlcyBkb25uw6llcyBkZSBwcm90w6lvbWlxdWUsIGRlcyBpZCBzb250IGR1cGxpcXXDqXMuIFBvdXIgcG91dm9pciBsZXMgdXRpbGlzZXIgY29tbWUgKnJvdy5uYW1lcyoKaWwgdmEgZmFsbG9pciBsZXMgcmVub21tZXIuCgpgYGB7ciBjb3JyZWN0aW5nX2R1cGxpY2F0ZV9pZH0KaWQgPC0gcGZhX3JhdyRpZCAgIyBleHRyYWN0cyB0aGUgaWQgCmlkIDwtIG1ha2UudW5pcXVlKGlkKSAjIG1ha2UgdGhlbSB1bmlxdWUgYnkgYXBwZW5kaW5nIG51bWJlcnMgYWZ0ZXIgZHVwbGljYXRlcwpyb3cubmFtZXMocGZhX3JhdykgPC0gaWQgIyB1c2UgdGhvc2UgdW5pcXVlIGlkIGFzIHJvd25hbWVzIGZvciB0aGUgcHJvdGVvbWljcyBkYXRhCnBmYV9yYXcgPC0gcGZhX3Jhd1ssIC0xXSAjIEkgdGFrZSBvdXQgdGhlIGZvcm1lciBpZCBjb2x1bW4KYGBgCgpMZXMgw6ljaGFudGlsbG9ucyBuZSBzb250IHBhcyBkYW5zIGxlIG3Dqm1lIG9yZHJlIGRhbnMgbGVzIG1ldGFkb25uw6llcyB0cmFuc2NyaXB0b21pcXVlcyBldCBsZXMgZG9ubsOpZXMuIEplIGNvcnJpZ2UgY2VsYSAhCgpgYGB7ciBvcmRlcmluZ190aGVfc2FtcGxlc30KcmVmX29yZGVyIDwtIHBhc3RlMChyZXAoYygibm9ybWFsIiwgImRheTEiLCAiZGF5MiIsICJkYXkzIiwgImRheTciLCAiZGF5MTQiKSwgZWFjaD0zKSwgIl8iLCAxOjMpCgpmYV9tZXRhIDwtIGZhX21ldGFbbWF0Y2gocmVmX29yZGVyLCBmYV9tZXRhJHNhbXBsZU5hbWUpLF0KZmFfcmF3IDwtIGZhX3Jhd1ssIG1hdGNoKHJlZl9vcmRlciwgY29sbmFtZXMoZmFfcmF3KSldCmBgYAoKCgojIEFuYWx5c2UgZOKAmWV4cHJlc3Npb24gZGlmZsOpcmVudGllbGxlIDoKCiMjIENvbnNpZ25lcyA6CgoqKkFuYWx5c2UgZOKAmWV4cHJlc3Npb24gZGlmZsOpcmVudGllbGxlIHBvdXIgbGVzIGRvbm7DqWVzIGRlIHByb3TDqW9taXF1ZSBldCB0cmFuc2NyaXB0b21pcXVlID0+IGlkZW50aWZpZXIgbGVzIGfDqG5lcy9wcm90w6lpbmVzIHNpZ25pZmljYXRpdmVtZW50IGRpZmbDqXJlbnRpZWxsZW1lbnQgZXhwcmltw6lzIGRhbnMgbGUgbW9kw6hsZSBGQSBlbiBjb21wYXJhbnQgRGF5IDcgw6AgRGF5IDAuKioKCgpKZSB2YWlzIHV0aWxpc2VyIFtERVNlcTJdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy92aWduZXR0ZXMvREVTZXEyL2luc3QvZG9jL0RFU2VxMi5odG1sKSBwb3VyIHRyYWl0ZXIgbGVzIGRvbm7DqWVzIHRyYW5zY3JpcHRvbWlxdWVzLgpBdmFudCBsZSB0cmFpdGVtZW50IHBhciBERVNlcTIgaWwgZmF1dCA6CgotcXVlIGxlcyBkb25uw6llcyBkZSBjb21wdGFnZSBzb2llbnQgYnJ1dGVzIGV0IGVudGnDqHJlcyAoamUgdmFpcyBjb25zaWTDqXJlciBpY2kgcXVlIGxlcyBkb25uw6llcyBkaXRlcyBfcmF3XyBkZXMgYXV0ZXVycyBsZSBzb250IGJpZW4gcsOpZWxsZW1lbnQuLi4pCgotdHJhbnNmb3JtZXIgbGUgZGF0YWZyYW1lIGRlIGNvbXB0YWdlIGVuIG1hdHJpY2UKCiMjIFRyYWl0ZW1lbnQgZGVzIGRvbm7DqWVzIGRlIHRyYW5zY3JpcHRvbWlxdWUKCmBgYHtyIERFU2VxMl9kYXRhc2V0fQoKZmFfcmF3X21hdHJpeCA8LSBhcy5tYXRyaXgoZmFfcmF3KQpmYV9yYXdfbWF0cml4IDwtIHJvdW5kKGZhX3Jhd19tYXRyaXgpCgpmYV9tZXRhX3NpbXBsaWZpZWQgPC0gZmFfbWV0YVssIGMoInNhbXBsZU5hbWUiLCAiY29uZGl0aW9uIildCgpmYV9kZXNlcSA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IGZhX3Jhd19tYXRyaXgsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbERhdGEgPSBmYV9tZXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbiA9IH5jb25kaXRpb24pCmBgYApSZXRyYWl0IGRlcyBnw6huZXMgcGFzIG91IHF1YXNpIHBhcyBleHByaW3DqXMgZGFucyBsZXMgZG9ubsOpZXMgdHJhbnNjcmlwdG9taXF1ZXMsIG9uIG5lIGdhcmRlIHF1ZSBjZXV4IGRvbnQgbGUgdG90YWwgZGUgY29tcHRhZ2VzIGVzdCBzdXDDqXJpZXVyIMOgIDEwLgoKYGBge3IgZmlsdGVyaW5nX3RyYW5zY3JpcHRvbWljfQojIHRha2luZyBvdXQgbG93IGV4cHJlc3Npb24gZ2VuZXMKZmFfZGVzZXEgPC0gZmFfZGVzZXFbIHJvd1N1bXMoY291bnRzKGZhX2Rlc2VxKSkgPiAxMCwgXQoKYGBgCgpMYSBmb25jdGlvbiBERVNlcSgpIHLDqWFsaXNlIHBsdXNpZXVycyDDqXRhcGVzIDoKCi0gbCdlc3RpbWF0aW9uIGRlcyBzaXplIGZhY3RvcnMsIHBvdXIgbm9ybWFsaXNlciBldCBwcmVuZHJlIGVuIGNvbXB0ZSBsYSB0YWlsbGUgZGVzIGxpYnJhcmllcyBkYW5zIGwnYW5hbHlzZQoKLSBsJ2VzdGltYXRpb24gZGUgbGEgZGlzcGVyc2lvbiBwb3VyIGNoYXF1ZSBnw6huZQoKLSB1biBmaXQgc3VyIHVuIG1vZMOobGUgbGluw6lhaXJlCgoKRW5zdWl0ZSBvbiBzJ2ludGVyZXNzZXJhIHF1J2F1eCBjb25kaXRpb25zICJub3JtYWwiIGV0ICJkYXk3IiBlbiBsZXMgZXh0cmF5YW50IGRlcyByw6lzdWx0YXRzLgoKCiMjIEFuYWx5c2UgZGlmZsOpcmVudGllbGxlIERFU2VxMiB0cmFuc2NyaXB0b21pcXVlCgpgYGB7ciBERVNlcTJfZGlmZmVyZW50aWFsX2FuYWx5c2lzLCBtZXNzYWdlPUZBTFNFfQoKZmFfZGVzZXEgPC0gREVTZXEoZmFfZGVzZXEpCgojIEV4dHJhY3RpbmcgZGlmZmVyZW50aWFsIGFuYWx5c2UgcmVzdWx0cyBmb3IgY29uZGl0aW9ucyBkYXk3IG9uIG5vcm1hbApyZXNfREVTZXEgPC0gcmVzdWx0cyhmYV9kZXNlcSwgY29udHJhc3QgPSBjKCJjb25kaXRpb24iLCAiZGF5NyIsICJub3JtYWwiKSkKCiMgc2VsZWN0aW5nIGdlbmVzIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCB3aXRoIGFuIGFkanVzdGVkIChCZW5qYW1pbmktSG9jaGJlcmcpIHAgdmFsdWUgdW5kZXIgNSUKc2lnbmlmaWNhbnRfZ2VuZXMgPC0gZGF0YS5mcmFtZShpZCA9IHJvdy5uYW1lcyhyZXNfREVTZXFbd2hpY2gocmVzX0RFU2VxJHBhZGogPCAwLjA1KSxdKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb2cyRm9sZENoYW5nZV90cmFuc2NyaXB0b21pY3MgPSByZXNfREVTZXFbd2hpY2gocmVzX0RFU2VxJHBhZGogPCAwLjA1KSwgImxvZzJGb2xkQ2hhbmdlIl0pCgpyb3cubmFtZXMoc2lnbmlmaWNhbnRfZ2VuZXMpIDwtIHNpZ25pZmljYW50X2dlbmVzJGlkCmBgYAoKCkonZXNzYWllIERFU2VxMiDDqWdhbGVtZW50IGF2ZWMgbGVzIGRvbm7DqWVzIGRlIHByb3TDqW9taXF1ZSwgY2FyIGNlbHVpLWNpIHBldXQgZW4gdGjDqW9yaWUgbGVzIHRyYWl0ZXIgKHVuZSBtZW50aW9uIGRpc2Nyw6h0ZSBkYW5zIGxhIGRvY3VtZW50YXRpb24pIGNlcGVuZGFudCBpbCBuJ2VzdCBwYXMgY29uc3RydWl0ICpwb3VyKiBsZXMgZG9ubsOpZXMgZGUgcHJvdMOpb21pcXVlIGNlIHF1aSBwb3VycmFpdCBwb3RlbnRpZWxsZW1lbnQgYmlhaXNlciBsZSByw6lzdWx0YXQuIE5vdXMgYWxsb25zIGVzc2F5ZXIgZGUgdm9pciBjZWxhLgoKIyMgVHJhaXRlbWVudCBkZXMgZG9ubsOpZXMgZGUgcHJvdMOpb21pcXVlCgpgYGB7ciBERVNlcTJfcHJvdGVvX2RhdGFzZXR9CgpwZmFfcmF3X21hdHJpeCA8LSBhcy5tYXRyaXgocGZhX3JhdykKcGZhX3Jhd19tYXRyaXggPC0gcm91bmQocGZhX3Jhd19tYXRyaXgpCgpwZmFfZGVzZXEgPC0gREVTZXFEYXRhU2V0RnJvbU1hdHJpeChjb3VudERhdGEgPSBwZmFfcmF3X21hdHJpeCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sRGF0YSA9IHBmYV9tZXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+IGNvbmRpdGlvbikKCmBgYAoKIyMgQW5hbHlzZSBkaWZmw6lyZW50aWVsbGUgREVTZXEyIHByb3TDqW9taXF1ZSA6CgpgYGB7ciBERVNlcTJfcHJvdGVvX2RpZmZlcmVudGlhbF9hbmFseXNpcywgbWVzc2FnZT1GQUxTRX0KcGZhX2Rlc2VxIDwtIERFU2VxKHBmYV9kZXNlcSkKcmVzX3Byb3RfREVTZXEyIDwtIHJlc3VsdHMocGZhX2Rlc2VxLCBjb250cmFzdCA9IGMoImNvbmRpdGlvbiIsICJkYXk3IiwgIm5vcm1hbCIpKQoKIyBTYXZpbmcgZmlsZSBmb3IgbmV0d29yayBhbmFseXNpcwp0bXBfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXNfcHJvdF9ERVNlcTIpCnRtcF9kZiA8LSBhZGRfY29sdW1uKC5kYXRhID0gdG1wX2RmLCBnZW5lX2lkID0gcm93Lm5hbWVzKHRtcF9kZiksIC5iZWZvcmUgPSAxKQoKd3JpdGVfY3N2KHRtcF9kZiwgZmlsZSA9ICJyZXN1bHRzL3Byb3RlaW5fbG9nRm9sZENoYW5nZV83LTAuY3N2IikKcmVtb3ZlKHRtcF9kZikKCnNpZ25pZmljYW50X3Byb3RlaW5zIDwtIGRhdGEuZnJhbWUoaWQgPSByb3cubmFtZXMocmVzX3Byb3RfREVTZXEyW3doaWNoKHJlc19wcm90X0RFU2VxMiRwYWRqIDwgMC4wNSksIF0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxvZzJGb2xkQ2hhbmdlX3Byb3Rlb21pY3MgPSByZXNfcHJvdF9ERVNlcTJbd2hpY2gocmVzX3Byb3RfREVTZXEyJHBhZGogPCAwLjA1KSwgImxvZzJGb2xkQ2hhbmdlIl0pCnJvdy5uYW1lcyhzaWduaWZpY2FudF9wcm90ZWlucykgPC1zaWduaWZpY2FudF9wcm90ZWlucyRpZApgYGAKCiMjIFZpc3VhbGlzYXRpb24gZGVzIHLDqXN1bHRhdHMgOgoKTGVzIGfDqG5lcyBvdSBwcm90w6lpbmVzIHNpZ25pZmljYXRpdmVtZW50IChwIHZhbHVlIGFkanVzdGVkIDwgMC4wNSUpIGRpZmbDqXJlbnRpZWxsZW1lbnQgZXhwcmltw6lzIHNvbnQgY29sb3LDqXMgZW4gYmxldS4KCmBgYHtyIHBsb3RfZGlmZmVyZW50aWFsX2FuYWx5c2VzX3Jlc3VsdHMsIGZpZy5jYXA9IkRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBnZW5lcyBhbmQgcHJvdGVpbnMuIn0KcGFyKG1mcm93PWMoMSwyKSkKcGxvdE1BKHJlc19ERVNlcSwgbGFzPTEsIGNleC5tYWluID0gMSwgeWxpbT1jKC04LDgpLAogICAgICAgbWFpbiA9ICJEaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMiKQpwbG90TUEocmVzX3Byb3RfREVTZXEyLCBsYXM9MSwgY2V4Lm1haW4gPSAxLCB5bGltPWMoLTgsOCksCiAgICAgICBtYWluID0gIkRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBwcm90ZWlucyIpCgpgYGAKCgoKYGBge3IgbWVyZ2luZ19yZXN1bHRzfQpzaWduaWZpY2FudF9hbGwgPC0gZnVsbF9qb2luKHNpZ25pZmljYW50X2dlbmVzLCBzaWduaWZpY2FudF9wcm90ZWlucywgYnk9ImlkIikKcm93Lm5hbWVzKHNpZ25pZmljYW50X2FsbCkgPC0gc2lnbmlmaWNhbnRfYWxsJGlkCgpwcmludChwYXN0ZTAoIkRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBSTkFzOiAiLCBkaW0oc2lnbmlmaWNhbnRfZ2VuZXMpWzFdKSkKCnByaW50KHBhc3RlMCgiRGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIHByb3RlaW5zOiAiLCBkaW0oc2lnbmlmaWNhbnRfcHJvdGVpbnMpWzFdKSkKCnByaW50KHBhc3RlMCgiR2VuZXMgd2l0aCBib3RoIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBSTkEgYW5kIHByb3RlaW46ICIsIGRpbShuYS5vbWl0KHNpZ25pZmljYW50X2FsbCkpWzFdKSkKCmthYmxlKG5hLm9taXQoc2lnbmlmaWNhbnRfYWxsKVsxOjEwLF0sIGNhcHRpb24gPSAiU29tZSBleGFtcGxlczoiKQoKCnN0cmFuZ2VfZ2VuZXMgPC0gIGMobmEub21pdChzaWduaWZpY2FudF9hbGxbd2hpY2goc2lnbmlmaWNhbnRfYWxsJGxvZzJGb2xkQ2hhbmdlX3RyYW5zY3JpcHRvbWljcyA+IDAgJiBzaWduaWZpY2FudF9hbGwkbG9nMkZvbGRDaGFuZ2VfcHJvdGVvbWljcyA8IDApLCAiaWQiXSksIG5hLm9taXQoc2lnbmlmaWNhbnRfYWxsW3doaWNoKHNpZ25pZmljYW50X2FsbCRsb2cyRm9sZENoYW5nZV90cmFuc2NyaXB0b21pY3MgPCAwICYgc2lnbmlmaWNhbnRfYWxsJGxvZzJGb2xkQ2hhbmdlX3Byb3Rlb21pY3MgPiAwKSwgImlkIl0pKQoKCmthYmxlKHNpZ25pZmljYW50X2FsbFtzdHJhbmdlX2dlbmVzLCAyOjNdLCBjYXB0aW9uID0gIlVuY29uc2lzdGFuY3kgYmV0d2VlbiBSTkEgYW5kIHByb3RlaW4iKQoKcHJpbnQocGFzdGUwKCJVbmNvbnNpc3RhbnQgZ2VuZXM6ICIsIChsZW5ndGgoc3RyYW5nZV9nZW5lcykgLyBucm93KHNpZ25pZmljYW50X2FsbCkgKiAxMDApLCAiICUiKSkKCmBgYAoKTGUgZGlhZ3JhbW1lIGRlIFZlbm4gY2ktZGVzc291cyBwcsOpc2VudGUgbCdpbnRlcnNlY3Rpb24gZW50cmUgbGVzIGfDqG5lcyBkaWZmw6lyZW50aWVsbGVtZW50IGV4cHJpbcOpcyBhdSBuaXZlYXUgQVJOIGV0IGNldXggYXUgbml2ZWF1IHByb3TDqWluZSwgZW50cmUgbGEgY29uZGl0aW9uIG5vcm1hbGUgZXQgZGF5Ny4gRW52aXJvbiA4NDAgZ8OobmVzIHZvaWVudCBsZXVyIG5pdmVhdSBzaWduaWZpY2F0aXZlbWVudCBkaWZmw6lyZW50IHBvdXIgbGVzIGRldXggdHlwZXMgZGUgbW9sw6ljdWxlLiAKCk9uIHBvdXJyYWl0IGVuc3VpdGUgcGFyIGV4ZW1wbGUgcsOpYWxpc2VyIHVuZSBhbmFseXNlIGQnZW5yaWNoaXNzZW1lbnQgZm9uY3Rpb25uZWwgc3VyIGNldHRlIGxpc3RlLgoKQXR0ZW50aW9uIGNlcGVuZGFudCwgY29tbWUgb24gcGV1dCBsZSBjb25zdGF0ZXIgZGFucyBsZXMgZXhlbXBsZXMgY2ktZGVzc3VzIHF1ZWxxdWVzIGfDqG5lcyBwZXV2ZW50IMOqdHJlIHN1cmV4cHJpbcOpcyBlbiBBUk4gZXQgc291cyBleHByaW3DqXMgZW4gcHJvdMOpaW5lcyAob3UgaW52ZXJzZW1lbnQpICEgSGV1cmV1c2VtZW50IGNlcyBnw6huZXMgYXV4IGV4cHJlc3Npb25zIGNvbnRyYWRpY3RvaXJlcyBzb250IHJhcmVzICEgRGlmZmljaWxlIGRlIGRpcmUgc2FucyBpbmZvcm1hdGlvbnMgY29tcGzDqW1lbnRhaXJlcyBzJ2lsIHBvdXJyYWl0IHMnYWdpciBkJ3VuZSByw6lhbGl0w6kgYmlvbG9naXF1ZSAocGFyIGV4ZW1wbGUgbGEgcHJvdMOpaW5lIHBvdXJyYWl0IMOqdHJlIHNvdW1pc2Ugw6AgdW5lIGTDqWdyYWRhdGlvbiB0csOocyBpbnRlbnNlIGV0IGxlIHJlaW4gcG91cnJhaXQgdGVudGVyIGRlIGNvbXBlbnNlciBlbiBwcm9kdWlzYW50IGJlYXVjb3VwIGQnQVJObSksIGQndW4gYmlhaXMgdGVjaG5pcXVlLCBvdSBiaWVuIHNpIGwndXRpbGlzYXRpb24gZGUgREVTZXEyIHN1ciBkZXMgZG9ubsOpZXMgZGUgcHJvdMOpb21pcXVlIHBvdXJyYWl0IGVudHJhaW5lciBkZXMgYXJ0w6lmYWN0cy4uLiBCZWF1Y291cCBkZSBwcm90w6lpbmVzIHNvbnQganVnw6llcyBkaWZmw6lyZW50aWVsbGVtZW50IGV4cHJpbcOpZXMgcGFyIERFU2VxMiBlbiBjb21wYXJhaXNvbiBhdXggQVJOLgoKCgpgYGB7ciBkaWZmZXJlbnRpYWxfYW5hbHlzZXNfdmlzdWFsaXNhdGlvbiwgZmlnLmNhcD0iRGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIGFuZCBwcm90ZWlucywgb3ZlcmxhcC4ifQoKYXJlYTEgPC0gZGltKHNpZ25pZmljYW50X3Byb3RlaW5zKVsxXQphcmVhMiA8LSBkaW0oc2lnbmlmaWNhbnRfZ2VuZXMpWzFdCmNyb3NzIDwtIGRpbShuYS5vbWl0KHNpZ25pZmljYW50X2FsbCkpWzFdCgp2ZW5uLnBsb3QgPC0gZHJhdy5wYWlyd2lzZS52ZW5uKGFyZWExID0gYXJlYTEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEyID0gYXJlYTIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNyb3NzLmFyZWEgPSBjcm9zcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb250ZmFtaWx5ID0gInNhbnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhdGVnb3J5ID0gYygic2lnbmlmaWNhbnQgcHJvdGVvbWljcyIsICJzaWduaWZpY2FudCB0cmFuc2NyaXB0b21pY3MiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYyhhbHBoYSgibWFnZW50YSIsMC4zKSwgYWxwaGEoJ2N5YW4nLDAuMykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGx3ZD0wLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhdC5jb2wgPSBjKCJtYWdlbnRhIiwgImN5YW4iKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYXQucG9zID0gYygwLCAwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYXQuZGlzdCA9IGMoMC4wMiwgMC4wODcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhdC5mb250ZmFtaWx5ID0gInNhbnMiKQpncmlkLmRyYXcodmVubi5wbG90KQpncmlkLm5ld3BhZ2UoKQoKYGBgCgoKCgoKIyBBbmFseXNlIG11bHRpLW9taXF1ZQoKIyMgQ29uc2lnbmVzIDoKCioqQW5hbHlzZSBtdWx0aS1vbWlxdWUgKHRyYW5zY3JpcHRvICsgcHJvdMOpbykgYXZlYywgYXUgY2hvaXgsIE1PRkEsIG1peE9taWNzLCBtaXhLZXJuZWwgb3UgZOKAmWF1dHJlcyBvdXRpbHMgZGUgZmFjdG9yaXNhdGlvbiBtdWx0aS1tYXRyaWNlcy4gVm91cyBwb3V2ZXogc29pdCB2b3VzIGZvY2FsaXNlciBzdXIgdW4gdGltZSBwb2ludCwgc29pdCBpbnTDqWdyZXIgbGVzIGRpZmbDqXJlbnRzIHRpbWUgcG9pbnRzLCBlbiBwYXJ0YW50IGRlcyBkb25uw6llcyBub3JtYWxpc8OpZXMgZm91cm5pZXMgZGFucyBsZSBtYXTDqXJpZWwgc3VwcGzDqW1lbnRhaXJlIGR1IHBhcGllci4qKgoKCkondXRpbGlzZSBtaXhPbWljcy4gQ2VwZW5kYW50IGplIHZhaXMgZXNzYXllciBkZSBub3JtYWxpc2VyIGxlcyBkb25uw6llcyBtb2ktbcOqbWUgKGVuIG0naW5zcGlyYW50IGRlIGNlbGxlcyB1dGlsaXPDqWVzIGRhbnMgbGUgbW9kdWxlIDMpIGV0IGRlIHJlZ2FyZGVyIHRvdXMgbGVzIHRpbWVtcG9pbnRzIHBvc3NpYmxlcyAow6AgcGFydCBsZSBqb3VyIDMgcXVpIGVzdCBhYnNlbnQgZGVzIGRvbm7DqWVzIGRlIHByb3TDqW9taXF1ZXMpLgoKQXZhbnQgZCdleHBsb3JlciBub3MgZG9ubsOpZXMgYXZlYyBtaXhPbWljcyBpbCBmYXV0IDoKCi0gbGVzIG5vcm1hbGlzZXIKCi0gbGVzIGZpbHRyZXIgKHBvdXIgYXZvaXIgbWF4aW11bSBlbnZpcm9uIDEwIDAwMCBnw6huZXMgb3UgcHJvdMOpaW5lcykKCi0gcGFzc2VyIGxlcyDDqWNoYW50aWxsb25zIGVuIGxpZ25lcyBldCBsZXMgZ8OobmVzL3Byb3TDqWluZXMgZW4gY29sb25uZXMKCi0gYXZvaXIgbGVzIG3Dqm1lcyDDqWNoYW50aWxsb25zIGRhbnMgbGVzIGRldXggYW5hbHlzZXMKClBvdXIgbGVzIGRvbm7DqWVzIGRlIHByb3TDqW9taXF1ZSwgb24gYSB1biBub21icmUgZCdvYnNlcnZhYmxlcyByYWlzb25uYWJsZSAoODA0NCksIGplIG5lIGZpbHRyZXJhaSBkb25jIHBhcyBkZSBwcm90w6lpbmVzLgoKRW4gcmV2YW5jaGUgcG91ciBsZXMgZG9ubsOpZXMgdHJhbnNjcmlwdG9taXF1ZXMsIG9uIGEgYmVhdWNvdXAgZCdBUk5zICg0NjY3OSksIGlsIHZhdXQgbWlldXggbGVzIGZpbHRyZXIgIQoKCgojIyBUcmFpdGVtZW50IGRlcyBkb25uw6llcwoKUmV0cmFpdCBkZXMgZ8OobmVzIHBhcyBvdSBxdWFzaSBwYXMgZXhwcmltw6lzIGRhbnMgbGVzIGRvbm7DqWVzIHRyYW5zY3JpcHRvbWlxdWVzLCBvbiBuZSBnYXJkZSBxdWUgY2V1eCBkb250IGxhIHNvbW1lIGRlcyBjb21wdGFnZSBlc3Qgc3Vww6lyaWV1cmUgw6AgMTAuCgpgYGB7ciBSTkFfZmlsdGVyaW5nfQpmYV9maWx0ZXJlZCA8LSBmYV9yYXdbd2hpY2gocm93U3VtcyhmYV9yYXcpID4gMTApLCBdCgpgYGAKCk5vcm1hbGlzYXRpb24gaW50ZXItw6ljaGFudGlsbG9uIHN1ciBsZXMgdHJvaXNpw6htZXMgcXVhcnRpbGVzCgpgYGB7ciBub3JtYWxpc2F0aW9uX29uXzNyZF9xdWFydGlsZX0KCnRoaXJkX3F1YW50aWxlcyA8LSBhcHBseShmYV9maWx0ZXJlZCwgMiAsIHF1YW50aWxlLCBwPTAuNzUsIG5hLnJtID0gVFJVRSkKZ2xvYmFsX3RoaXJkX3F1YW50aWxlIDwtIHF1YW50aWxlKHVubGlzdChmYV9maWx0ZXJlZCksIHA9MC43NSkKc2l6ZV9mYWN0b3JzIDwtIDEgLyB0aGlyZF9xdWFudGlsZXMgKiBnbG9iYWxfdGhpcmRfcXVhbnRpbGUKCgpmYV9ub3JtIDwtIHQodChmYV9maWx0ZXJlZCkgKiBzaXplX2ZhY3RvcnMpCgpgYGAKClRyYW5zZm9ybWF0aW9uIGVuIGxvZzIKCmBgYHtyIGxvZzJfdHJhbnNmb3JtYXRpb259CmZhX2xvZzIgPC0gbG9nMihmYV9ub3JtICsgMSkKCiNib3hwbG90KGZhX2xvZzIsIGNvbD1mYV9tZXRhJGNvbG9yKQpgYGAKCkNhbGN1bHMgZGUgc3RhdGlzdGlxdWVzIHN1ciBsZXMgZ8OobmVzIHBvdXIgbmUgZ2FyZGVyIHF1ZSBsZXMgZ8OobmVzIGxlcyBwbHVzIGV4cHJpbcOpcyBkYW5zIGwnYW5hbHlzZS4KCmBgYHtyIGdlbmVfc3RhdHNfZm9yX3NlbGVjdGlvbn0KZ2VuZV9zdGF0cyA8LSBkYXRhLmZyYW1lKHJvdy5uYW1lcyA9IHJvdy5uYW1lcyhmYV9sb2cyKSkKZ2VuZV9zdGF0cyRtZWFuIDwtIGFwcGx5KGZhX2xvZzIsIDEsIG1lYW4pCmdlbmVfc3RhdHMkdmFyIDwtIGFwcGx5KGZhX2xvZzIsIDEsIHZhcikKYGBgClNlbGVjdGlvbiBkZXMgZ8OobmVzIGV4cHJpbcOpcyBsZXMgcGx1cyB2YXJpYWJsZXMuCgpgYGB7ciBzZWxlY3RpbmdfaW50ZXJlc3RfZ2VuZXN9CmdlbmVfa2VwdCA8LSBnZW5lX3N0YXRzW3doaWNoKChnZW5lX3N0YXRzJG1lYW4gPiA0KSAmIChnZW5lX3N0YXRzJHZhciA+IDEuNSkpLCBdCmZhX2xvZzIgPC0gZmFfbG9nMltyb3cubmFtZXMoZ2VuZV9rZXB0KSxdCgojYm94cGxvdChmYV9sb2cyLCBjb2w9ZmFfbWV0YSRjb2xvcikKYGBgCgpOb3JtYWxpc2F0aW9uIGludGVyLcOpY2hhbnRpbGxvbnMgZGVzIGRvbm7DqWVzIGRlIHByb3TDqW9taXF1ZQoKYGBge3Igbm9ybWFsaXNhdGlvbl9wcm90ZW9taWNzfQoKdGhpcmRfcXVhbnRpbGVzIDwtIGFwcGx5KHBmYV9yYXcsIDIgLCBxdWFudGlsZSwgcD0wLjc1LCBuYS5ybSA9IFRSVUUpCmdsb2JhbF90aGlyZF9xdWFudGlsZSA8LSBxdWFudGlsZSh1bmxpc3QocGZhX3JhdyksIHA9MC43NSkKc2l6ZV9mYWN0b3JzIDwtIDEgLyB0aGlyZF9xdWFudGlsZXMgKiBnbG9iYWxfdGhpcmRfcXVhbnRpbGUKCgpwZmFfbm9ybSA8LSB0KHQocGZhX3JhdykgKiBzaXplX2ZhY3RvcnMpCgpgYGAKClRyYW5zZm9ybWF0aW9uIGRlcyBkb25uw6llcyBkZSBwcm90w6lvbWlxdWUgZW4gbG9nMgoKYGBge3IgdHJhbnNmb3JtYXRpb25fcHJvdGVvbWljc30KCnBmYV9sb2cyIDwtIGxvZzIocGZhX25vcm0gKyAxKQoKI2JveHBsb3QocGZhX2xvZzIsIGNvbD1wZmFfbWV0YSRjb2xvcikKCmBgYAoKCkplIHZhaXMgcmV0aXJlciBsZXMgcsOpcGxpY2F0cyBiaW9sb2dpcXVlIG7CsDMsIGFmaW4gZCdhdm9pciBsZXMgbcOqbWUgaW5kaXZpZHVzIGRhbnMgbGVzIGRldXggamV1eCBkZSBkb25uw6llcy4gSmUgcmV0aXJlIMOpZ2FsZW1lbnQgbGVzIHRpbWVwb2ludHMgZHUgam91ciAzLCBxdWkgc29udCBhYnNlbnRzIGR1IGpldSBwcm90w6lvbWlxdWUuCgpgYGB7ciBkYXRhX2lucHV0X21peE9taWNzfQoKZmFfbWl4IDwtIGZhX2xvZzJbLCAtYygzLCA2LCA5LCAxMCwgMTEsIDEyLCAxNSwgMTgpXQptZXRhX21peCA8LSBmYV9tZXRhWy1jKDMsIDYsIDksIDEwLCAxMSwgMTIsIDE1LCAxOCksLTFdCgptdWx0aV9kYXRhc2V0IDwtIGxpc3QoZ2VuZXMgPSB0KGZhX21peCksCiAgICAgICAgICAgICAgICAgICAgICBwcm90ZWlucyA9IHQocGZhX2xvZzIpLAogICAgICAgICAgICAgICAgICAgICAgY29uZGl0aW9ucyA9IG1ldGFfbWl4KQoKYGBgCgojIyBTcGFyc2UgUGFydGlhbCB0byBMZWFzdCBTcXVhcmVzIChzUExTKQoKQ2V0dGUgbcOpdGhvZGUgZXN0IG5vbi1zdXBlcnZpc8OpZSwgbGVzIMOpY2hhbnRpbGxvbnMgbmUgc29udCBwYXMgc8OpcGFyw6lzIGVuIGdyb3VwZXMuCkxhIHNwYXJzZSBQTFMgdmEgaW50w6lncmVyIGxlcyBkb25uw6llcyBkZSB0cmFuc2NyaXB0b21pcXVlIGV0IGRlIHByb3TDqW9taXF1ZSB0b3V0IGVuIHPDqWxlY3Rpb25uYW50IGxlcyB2YXJpYWJsZXMgKGfDqG5lcyBvdSBwcm90w6lpbmVzKSBsZXMgcGx1cyBjb3ZhcmlhbnRzIG91IGFudGktY292YXJpYW50cyBkYW5zIGxlcyBkZXV4IGpldXggZGUgZG9ubsOpZXMgb21pcXVlcy4KCmBgYHtyIHNQTFMsIGZpZy5jYXA9InNQTFMgb24gdHJhbnNjcmlwdG9taWMgYW5kIHByb3Rlb21pYyBkYXRhc2V0cyJ9CgptaXhfc1BMUyA8LSBzcGxzKFggPSBtdWx0aV9kYXRhc2V0JGdlbmVzLAogICAgICAgICAgICAgICAgIFkgPSBtdWx0aV9kYXRhc2V0JHByb3RlaW5zLAogICAgICAgICAgICAgICAgIGtlZXBYPSBjKDI1LCAyNSksCiAgICAgICAgICAgICAgICAga2VlcFk9IGMoMjUsIDI1KSkKCnBsb3RJbmRpdihtaXhfc1BMUywgcmVwLnNwYWNlID0gJ1hZLXZhcmlhdGUnLCBjZXg9My41LAogICAgICAgICAgZ3JvdXAgPSBtdWx0aV9kYXRhc2V0JGNvbmRpdGlvbnMkY29uZGl0aW9uLAogICAgICAgICAgdGl0bGUgPSAiSW5kaXZpZHVhbHMgU3BhcnNlIFBMUyIpCgpgYGAKCkljaSwgamUgZGVtYW5kZSDDoCBsYSBtw6l0aG9kZSBkZSBnYXJkZXIgMjUgdmFyaWFibGVzIGRlIGNoYXF1ZSBqZXUgZGUgZG9ubsOpZXMgcGFyIGNvbXBvc2FudGUuIEplIGRlbWFuZGUgZCdpbmNsdXJlIGRldXggY29tcG9zYW50ZXMgZGFucyBsZSBtb2TDqGxlLgoKTGUgcGxvdCBwcsOpc2VudGUgdW5lIHN5bnRow6hzZSBkZSBsJ2FuYWx5c2UgZGVzIGRldXggamV1eCBkZSBkb25uw6llcy4KCk9uIGNvbnN0YXRlIHF1ZSBsYSBzUExTIG5vdXMgcGVybWV0IG5vbiBzZXVsZW1lbnQgZCfDqXR1ZGllciBsYSByZWxhdGlvbiBlbnRyZSBub3MgZGV1eCBqZXV4IGRlIGRvbm7DqWVzLCBtYWlzIGljaSBwZXJtZXQgZGUgc8OpcGFyZXIgIm5hdHVyZWxsZW1lbnQiIChzYW5zIHN1cGVydmlzaW9uKSBsZXMgw6ljaGFudGlsbG9ucyBlbiAiY2x1c3RlcnMiIGRpc3RpbmN0cy4gTGVzIG5vcm1hdXggaXNvbMOpcywgbGVzIGNvbmRpdGlvbnMgcGV1IGFwcsOocyBsJ2luamVjdGlvbiBwcm9jaGVzIGV0IGNlbGxlcyBwbHVzIHRhcmRpdmVzIGVuc2VtYmxlLgoKYGBge3IgbG9hZGluZ3MsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTYsIG91dC53aWR0aD0iODAlIiwgZmlnLmNhcD0ic1BMUzogTG9hZGluZ3Mgb24gZ2VuZXMgYW5kIHByb3RlaW5zLiJ9CnBhcihtZnJvdz1jKDEsMikpCnBsb3RMb2FkaW5ncyhtaXhfc1BMUywgc2l6ZS50aXRsZT0xLjIsIGNvbXA9MSwgCiAgICAgICAgICAgICB0aXRsZSA9ICJMb2FkaW5ncyBvbiBjb21wIDEiLAogICAgICAgICAgICAgc3VidGl0bGUgPSBjKCJSTkFzIiwgIlByb3RlaW5zIiksCiAgICAgICAgICAgICBzaXplLnN1YnRpdGxlID0gMSkKcGxvdExvYWRpbmdzKG1peF9zUExTLCBzaXplLnRpdGxlPTEuMiwgY29tcD0yLAogICAgICAgICAgICAgdGl0bGUgPSAiTG9hZGluZ3Mgb24gY29tcCAyIiwKICAgICAgICAgICAgIHN1YnRpdGxlID0gYygiUk5BcyIsICJQcm90ZWlucyIpLAogICAgICAgICAgICAgc2l6ZS5zdWJ0aXRsZSA9IDEpCgpgYGAKCkljaSBsZXMgbG9hZGluZyByZXByw6lzZW50ZW50IHF1ZWxzIGfDqG5lcyBjb250cmlidWVudCBsZSBwbHVzIMOgIGxhIGNvdmFyaWFuY2UgZW50cmUgbGVzIGRldXggamV1eCBkZSBkb25uw6llcyBvbWlxdWVzLiBMZSBibG9jIFggcmVwcsOpc2VudGUgbGVzIGfDqG5lcyBkb250IGwnZXhwcmVzc2lvbiBjYXJhY3TDqXJpc2UgbGEgcmVsYXRpb24gZW50cmUgZG9ubsOpZXMgdHJhbnNjcmlwdG9taXF1ZXMgZXQgcHJvdMOpb21pcXVlcywgbGUgYmxvYyBZIGxlcyBwcm90w6lpbmVzLgoKT24gb2J0aWVudCBkb25jIGRlcyBsaXN0ZXMgZGUgZ8OobmVzIGV0IHByb3TDqWluZXMgcXVpIHBvdXJyb250IGNhcmFjdMOpcmlzZXIgbGVzIMOpY2hhbnRpbGxvbnMuCgojIFLDqXNlYXUgZGUgY28tZXhwcmVzc2lvbiBhdmVjIFdHQ05BCgojIyBDb25zaWduZXMgOgoKKipDb25zdHJ1Y3Rpb24gZGUgcsOpc2VhdSBhdmVjIFdHQ05BIMOgIHBhcnRpciBkZXMgZG9ubsOpZXMgdHJhbnNjcmlwdG9taXF1ZXMuKioKCioqUmVjb25zdHJ1Y3QgdGhlIGNvLWV4cHJlc3Npb24gbmV0d29yayBmcm9tIGFsbCB0aGUgdGltZSBwb2ludHMgb2YgdGhlIEZBIHRyYW5zY3JpcHRvbWljcyBkYXRhLiBQcm9wb3NlIHRvIGZpbHRlciBhbmQgcmVtb3ZlIGFsbCB0aGUgemVybyBleHByZXNzZWQgZ2VuZXMsIHRoZSBOQXMgYW5kIHRoZSBsZXNzIGluZm9ybWF0aXZlIGdlbmVzIGZyb20gdGhlIHRyYW5zY3JpcHRvbWljcyBkYXRhLiAoSSByZW1vdmUgYWxsIHRoZSBnZW5lcyB0aGF0IGFyZSBub3QgZXhwcmVzc2VkIGluIGF0IGxlYXN0IDkgb3V0IG9mIHRoZSAxOCBjb25kaXRpb25zIChleHByZXNzaW9uID4gMSBUUE0gaW4gOSkgYW5kIHRoZW4gZmlsdGVyIHdpdGggdGhlIGNvZWZmaWNpZW50IG9mIHZhcmlhdGlvbiA+IDAuNzUpLioqCgoqKlRoZW4gYXBwbHkgdGhlIGZpcnN0IHBhcnQgb2YgdGhlIG5ldHdvcmsgcmVjb25zdHJ1Y3Rpb24gc3RlcHMgYXMgd2Ugc2F3IHRoZW0gb24gdGhlIFdHQ05BIGNvdXJzZSB1bnRpbCB0aGUgbW9kdWxlIHByZWRpY3Rpb25zLioqCgoqKkluc3RlYWQgb2YgdXNpbmcgV0dDTkHigJlzIG1vZHVsZSBwcmVkaWN0aW9uIHJvdXRpbmVzLCBhcHBseSBhIHVuaXZlcnNhbCB0aHJlc2hvbGQgb2YgMC41IG9uIHRoZSBhZGphY2VuY3kgbWF0cml4LCBhbmQgb2J0YWluIGFuIGFkamFjZW5jeSBtYXRyaXggdGhhdCBpcyByZWR1Y2VkIGluIHNpemUuIFRoaXMgaXMgdGhlIG5ldHdvcmsuIEltcG9ydCBpdCB0byBDeXRvc2NhcGUgd2l0aCBhTWF0UmVhZGVyIHBsdWdpbi4KVmlzdWFsaXplLCBhbmFseXplIHRoZSBuZXR3b3JrIGFuZCBzdXBlcmltcG9zZSB0aGUgcHJvdGVvbWljcyBkYXRhIG9uIGl0LioqCgoKIyMgRmlsdHJhZ2UgZGVzIGRvbm7DqWVzIGRlIHRyYW5zY3JpcHRvbWlxdWUKCmBgYHtyIGZpbHRlcmluZ19mb3JfV0dDTkF9CgojIENvbXB1dGluZyBnZW5lIHN0YXRpc3RpY3MKc3RhdHNfd2djbmEgPC0gZGF0YS5mcmFtZShtZWFuID0gYXBwbHkoZmFfcmF3LCAxLCBtZWFuKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzZCA9IGFwcGx5KGZhX3JhdywgMSwgc2QpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IHJvdy5uYW1lcyhmYV9yYXcpKQoKc3RhdHNfd2djbmEkY29lZl92YXIgPC0gKHN0YXRzX3dnY25hJHNkIC8gc3RhdHNfd2djbmEkbWVhbikKc3RhdHNfd2djbmEkbnVsbCA9IGFwcGx5KGZhX3JhdyA9PSAwLCAxLCBzdW0sIG5hLnJtID0gVFJVRSkKCgojIGZpbHRlcmluZyBhdCBsZWFzdCBleHByZXNzZWQgaW4gOSBzYW1wbGVzIGFuZCBjb2VmZmljaWVudCBvZiB2YXJpYXRpb24gc3VwZXJpb3IgdG8gMC43NQoKZmFfd2djbmEgPC0gZmFfcmF3WyByb3cubmFtZXMoc3RhdHNfd2djbmFbIHdoaWNoKHN0YXRzX3dnY25hJG51bGwgPCA5ICYgc3RhdHNfd2djbmEkY29lZl92YXIgPiAwLjc1KSwgXSksIF0KCmBgYAoKSmUgcsOpYWxpc2UgYXVzc2kgdW5lIG5vcm1hbGlzYXRpb24gZXQgdW5lIHRyYW5zZm9ybWF0aW9uIGxvZzIsIGNhciBzaW5vbiBsZSBjbHVzdGVyaW5nIGVzdCB0csOocyBkaWZmw6lyZW50IGRlIGNlbHVpIG9idGVudSBsb3JzIGR1IG1pbmktcHJvamV0IGR1IG1vZHVsZSAzLgoKYGBge3IgV0dDTkFfbm9ybWFsaXNhdGlvbl9vbl8zcmRfcXVhcnRpbGV9Cgp0aGlyZF9xdWFudGlsZXMgPC0gYXBwbHkoZmFfd2djbmEsIDIgLCBxdWFudGlsZSwgcD0wLjc1LCBuYS5ybSA9IFRSVUUpCmdsb2JhbF90aGlyZF9xdWFudGlsZSA8LSBxdWFudGlsZSh1bmxpc3QoZmFfd2djbmEpLCBwPTAuNzUpCnNpemVfZmFjdG9ycyA8LSAxIC8gdGhpcmRfcXVhbnRpbGVzICogZ2xvYmFsX3RoaXJkX3F1YW50aWxlCgpmYV93Z2NuYSA8LSB0KHQoZmFfd2djbmEpICogc2l6ZV9mYWN0b3JzKQpgYGAKCmBgYHtyIFdHQ05BX2xvZzJfdHJhbnNmb3JtYXRpb259CmZhX3dnY25hIDwtIGxvZzIoZmFfd2djbmEgKyAxKQpgYGAKCgpgYGB7ciBzYW1wbGVfdHJlZV9nZW5lcmF0aW9ufQpzYW1wbGVfdHJlZSA9IGhjbHVzdChkaXN0KHQoZmFfd2djbmEpKSwgbWV0aG9kID0gImNvbXBsZXRlIikKCnBsb3Qoc2FtcGxlX3RyZWUsIG1haW4gPSAiU2FtcGxlIGNsdXN0ZXJpbmcgdG8gZGV0ZWN0IG91dGxpZXJzIiwgCiAgICAgeGxhYj0iIiwgY2V4Lm1haW4gPSAxKQoKYWJsaW5lKGggPSAyNTAsIGNvbCA9ICJyZWQiKQoKIyBGb3IgbWUgZGF5Ml8yIGlzIGFuIG91dGxpZXIsIEkgdGFrZSBpdCBvdXQuCmZhX3dnY25hIDwtIGZhX3dnY25hWywtOF0KCnNhbXBsZV90cmVlMiA8LSBoY2x1c3QoZGlzdCh0KGZhX3dnY25hKSksIG1ldGhvZCA9ICJjb21wbGV0ZSIpCgpgYGAKCkplIHJldGlyZSBsJ8OpY2hhbnRpbGxvbiBkYXkyXzIgcXVpIGVzdCB1biBvdXRsaWVyCgpNYWludGVuYW50IGplIGNyw6llIHVuIGRhdGEuZnJhbWUgYXZlYyBkZXMgZG9ubsOpZXMgYmluYWlyZXMsIHJlZmzDqXRhbnQgbGUgdGVtcHMgZGUgcHLDqWzDqHZlbWVudC4KCmBgYHtyIGdlbmVyYXRpbmdfdHJhaXRzX2RhdGF9Cgp0cmFpdHNfZGF0YSA8LSBkYXRhLmZyYW1lKG5hbWVzID0gZmFfbWV0YSRzYW1wbGVOYW1lKQoKdHJhaXRzX2RhdGEkbm9ybWFsIDwtIGFzLmludGVnZXIoc3RhcnRzV2l0aCh0cmFpdHNfZGF0YSRuYW1lcywgIm5vcm1hbCIpKQp0cmFpdHNfZGF0YSRkYXkxIDwtIGFzLmludGVnZXIoc3RhcnRzV2l0aCh0cmFpdHNfZGF0YSRuYW1lcywgImRheTFfIikpCnRyYWl0c19kYXRhJGRheTIgPC0gYXMuaW50ZWdlcihzdGFydHNXaXRoKHRyYWl0c19kYXRhJG5hbWVzLCAiZGF5MiIpKQp0cmFpdHNfZGF0YSRkYXkzIDwtIGFzLmludGVnZXIoc3RhcnRzV2l0aCh0cmFpdHNfZGF0YSRuYW1lcywgImRheTMiKSkKdHJhaXRzX2RhdGEkZGF5NyA8LSBhcy5pbnRlZ2VyKHN0YXJ0c1dpdGgodHJhaXRzX2RhdGEkbmFtZXMsICJkYXk3IikpCnRyYWl0c19kYXRhJGRheTE0IDwtIGFzLmludGVnZXIoc3RhcnRzV2l0aCh0cmFpdHNfZGF0YSRuYW1lcywgImRheTE0IikpCgpyb3cubmFtZXModHJhaXRzX2RhdGEpIDwtIHRyYWl0c19kYXRhJG5hbWVzCnRyYWl0c19kYXRhIDwtIHRyYWl0c19kYXRhWywtMV0KdHJhaXRzX2RhdGEgPC0gdHJhaXRzX2RhdGFbLTgsXQpgYGAKCiMjIFRyYWl0cyBldCDDqWNoYW50aWxsb25zCgpMZXMgZGlmZsOpcmVudHMgdGVtcHMgZGUgcHLDqWzDqHZlbWVudCBjb3Jyw6hsZW50IHBsdXTDtHQgYmllbiBhdmVjIGNlIHF1aSBlc3QgYXR0ZW5kdSBwb3VyIGxlcyDDqWNoYW50aWxsb25zLgoKYGBge3IgaGVhdF9tYXB9CiMgQ29udmVydCB0cmFpdHMgdG8gYSBjb2xvciByZXByZXNlbnRhdGlvbjogcmVkIG1lYW5zIHllcwp0cmFpdHNfY29sb3JzID0gbnVtYmVyczJjb2xvcnModHJhaXRzX2RhdGEsIHNpZ25lZCA9IEZBTFNFKTsKCiMgUGxvdCB0aGUgc2FtcGxlIGRlbmRyb2dyYW0gYW5kIHRoZSBjb2xvcnMgdW5kZXJuZWF0aC4KcGxvdERlbmRyb0FuZENvbG9ycyhzYW1wbGVfdHJlZTIsIHRyYWl0c19jb2xvcnMsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXBMYWJlbHMgPSBuYW1lcyh0cmFpdHNfZGF0YSksCiAgICAgICAgICAgICAgICAgICAgbWFpbiA9ICJTYW1wbGUgZGVuZHJvZ3JhbSBhbmQgdHJhaXRzIGhlYXRtYXAiKQpgYGAKCkplIHRyYW5zcG9zZSBsYSBtYXRyaWNlIGRlIGRvbm7DqWVzIGQnZXhwcmVzc2lvbiBwb3VyIGxhIHN1aXRlIGRlIGwnYW5hbHlzZS4KCmBgYHtyIHRyYW5zcG9zaW5nX2RhdGF9CgpmYV93Z2NuYSA8LSB0KGZhX3dnY25hKQoKYGBgCgojIyBXR0NOQSBjb25zdHJ1Y3Rpb24gZHUgcsOpc2VhdQoKIyMjIFJlY2hlcmNoZSBkJ3VuZSBwdWlzc2FuY2UgYWRhcHTDqWUKCmBgYHtyIGZvbGxvd2luZ193Z2NuYV9jb3Vyc2VfcGlwZWxpbmVfMSwgZmlnLmNhcD0iU29mdCB0aHJlc2hvbGRpbmciLCBtZXNzYWdlPUZBTFNFfQoKc2F2ZShmYV93Z2NuYSwgdHJhaXRzX2RhdGEsIGZpbGUgPSAicmVzdWx0cy9mYV90cmFuc2NyaXB0b21pY3Nfd2djbmEuUkRhdGEiKQoKYWxsb3dXR0NOQVRocmVhZHMoKQoKIyBMb2FkIHRoZSBkYXRhIHNhdmVkIGluIHRoZSBmaXJzdCBwYXJ0CmxuYW1lcyA9IGxvYWQoZmlsZSA9ICJyZXN1bHRzL2ZhX3RyYW5zY3JpcHRvbWljc193Z2NuYS5SRGF0YSIpOwoKCiMgQ2hvb3NlIGEgc2V0IG9mIHNvZnQtdGhyZXNob2xkaW5nIHBvd2Vycwpwb3dlcnMgPSBjKGMoMToxMCksIHNlcShmcm9tID0gMTIsIHRvID0gMjAsIGJ5ID0gMikpCgojIENhbGwgdGhlIG5ldHdvcmsgdG9wb2xvZ3kgYW5hbHlzaXMgZnVuY3Rpb24Kc2Z0ID0gcGlja1NvZnRUaHJlc2hvbGQoZmFfd2djbmEsIHBvd2VyVmVjdG9yID0gcG93ZXJzLCB2ZXJib3NlID0gMCkKCgoKIyBQbG90IHRoZSByZXN1bHRzOgpwYXIobWZyb3cgPSBjKDEsIDIpKTsKb3B0aW9ucyhyZXByLnBsb3Qud2lkdGggPSAxNCwgcmVwci5wbG90LmhlaWdodCA9IDEwKTsKCiMgU2NhbGUtZnJlZSB0b3BvbG9neSBmaXQgaW5kZXggYXMgYSBmdW5jdGlvbiBvZiB0aGUgc29mdC10aHJlc2hvbGRpbmcgcG93ZXIKcGxvdChzZnQkZml0SW5kaWNlc1ssMV0sIC1zaWduKHNmdCRmaXRJbmRpY2VzWywzXSkqc2Z0JGZpdEluZGljZXNbLDJdLAogICAgIHhsYWIgPSAiU29mdCBUaHJlc2hvbGQgKHBvd2VyKSIsIHlsYWIgPSAiU2NhbGUgRnJlZSBUb3BvbG9neSBNb2RlbCBGaXQsc2lnbmVkIFJeMiIsIHR5cGUgPSAibiIsCiAgICAgbWFpbiA9IHBhc3RlKCJTY2FsZSBpbmRlcGVuZGVuY2UiKSk7CnRleHQoc2Z0JGZpdEluZGljZXNbLDFdLCAtc2lnbihzZnQkZml0SW5kaWNlc1ssM10pKnNmdCRmaXRJbmRpY2VzWywyXSwKICAgICBsYWJlbHMgPSBwb3dlcnMsIGNleCA9IDAuOSwgY29sID0gInJlZCIpOwojIHRoaXMgbGluZSBjb3JyZXNwb25kcyB0byB1c2luZyBhbiBSXjIgY3V0LW9mZiBvZiBoCmFibGluZShoID0gMC45MCwgY29sID0gInJlZCIpCgojIE1lYW4gY29ubmVjdGl2aXR5IGFzIGEgZnVuY3Rpb24gb2YgdGhlIHNvZnQtdGhyZXNob2xkaW5nIHBvd2VyCnBsb3Qoc2Z0JGZpdEluZGljZXNbLDFdLCBzZnQkZml0SW5kaWNlc1ssNV0sCiAgICAgeGxhYiA9ICJTb2Z0IFRocmVzaG9sZCAocG93ZXIpIiwgeWxhYiA9ICJNZWFuIENvbm5lY3Rpdml0eSIsIHR5cGUgPSAibiIsCiAgICAgbWFpbiA9IHBhc3RlKCJNZWFuIGNvbm5lY3Rpdml0eSIpKQp0ZXh0KHNmdCRmaXRJbmRpY2VzWywxXSwgc2Z0JGZpdEluZGljZXNbLDVdLCBsYWJlbHMgPSBwb3dlcnMsIGNleCA9IDAuOSwgY29sID0gInJlZCIpCgpgYGAKCkplIHByZW5kcyBsZSBwb3dlciA2IHBvdXIgbGEgc3VpdGUgZHUgcGlwZWxpbmUuCgojIyMgUHLDqWRpY3Rpb24gZGUgbW9kdWxlcwoKYGBge3IgZm9sbG93aW5nX3dnY25hX2NvdXJzZV9waXBlbGluZV8yLCBtZXNzYWdlPUZBTFNFLCBmaWcuY2FwPSJEZW5kcm9ncmFtIGFuZCBtb2R1bGVzLiJ9CgpuZXQgPSBibG9ja3dpc2VNb2R1bGVzKGZhX3dnY25hLCBwb3dlciA9IDYsCiAgICAgICAgICAgICAgICAgICAgICAgVE9NVHlwZSA9ICJzaWduZWQiLCBtaW5Nb2R1bGVTaXplID0gMzAsCiAgICAgICAgICAgICAgICAgICAgICAgcmVhc3NpZ25UaHJlc2hvbGQgPSAxZS02LCBtZXJnZUN1dEhlaWdodCA9IDAuMjUsCiAgICAgICAgICAgICAgICAgICAgICAgbnVtZXJpY0xhYmVscyA9IFRSVUUsIHBhbVJlc3BlY3RzRGVuZHJvID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVRPTXMgPSBUUlVFLCBuVGhyZWFkcyA9IDgsCiAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVRPTUZpbGVCYXNlID0gInJlc3VsdHMvZmFfdHJhbnNjcmlwdG9taWNfVE9NIiwKICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gMCkKCiMgQ29udmVydCBsYWJlbHMgdG8gY29sb3JzIGZvciBwbG90dGluZwptZXJnZWRDb2xvcnMgPSBsYWJlbHMyY29sb3JzKG5ldCRjb2xvcnMpCiNtZXJnZWRDb2xvcnMKIyBQbG90IHRoZSBkZW5kcm9ncmFtIGFuZCB0aGUgbW9kdWxlIGNvbG9ycyB1bmRlcm5lYXRoCnBsb3REZW5kcm9BbmRDb2xvcnMobmV0JGRlbmRyb2dyYW1zW1sxXV0sIG1lcmdlZENvbG9yc1tuZXQkYmxvY2tHZW5lc1tbMV1dXSwKICAgICAgICAgICAgICAgICAgICAiTW9kdWxlIGNvbG9ycyIsCiAgICAgICAgICAgICAgICAgICAgZGVuZHJvTGFiZWxzID0gRkFMU0UsIGhhbmcgPSAwLjAzLAogICAgICAgICAgICAgICAgICAgIGFkZEd1aWRlID0gVFJVRSwgZ3VpZGVIYW5nID0gMC4wNSkKCm1vZHVsZUxhYmVscyA9IG5ldCRjb2xvcnMKbW9kdWxlQ29sb3JzID0gbGFiZWxzMmNvbG9ycyhuZXQkY29sb3JzKQpNRXMgPSBuZXQkTUVzOwpnZW5lVHJlZSA9IG5ldCRkZW5kcm9ncmFtc1tbMV1dOwpzYXZlKE1FcywgbW9kdWxlTGFiZWxzLCBtb2R1bGVDb2xvcnMsIGdlbmVUcmVlLAogICAgIGZpbGUgPSAicmVzdWx0cy9mYV90cmFuc2NyaXB0b21pY19uZXR3b3JrQ29uc3RydWN0aW9uLWF1dG8uUkRhdGEiKQoKCgpsbmFtZXMgPSBsb2FkKGZpbGUgPSAicmVzdWx0cy9mYV90cmFuc2NyaXB0b21pY3Nfd2djbmEuUkRhdGEiKTsKCiMgTG9hZCBuZXR3b3JrIGRhdGEgc2F2ZWQgaW4gdGhlIHNlY29uZCBwYXJ0LgpsbmFtZXMgPSBsb2FkKGZpbGUgPSAicmVzdWx0cy9mYV90cmFuc2NyaXB0b21pY19uZXR3b3JrQ29uc3RydWN0aW9uLWF1dG8uUkRhdGEiKTsKYGBgCgoKYGBge3IgZm9sbG93aW5nX3dnY25hX2NvdXJzZV9waXBlbGluZV8zLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTUsIG91dC53aWR0aD0iNjAlIiwgZmlnLmNhcD0iTW9kdWxlLXRyYWl0IHJlbGF0aW9uc2hpcHMifQoKIyBEZWZpbmUgbnVtYmVycyBvZiBnZW5lcyBhbmQgc2FtcGxlcwpuR2VuZXMgPSBuY29sKGZhX3dnY25hKTsKblNhbXBsZXMgPSBucm93KGZhX3dnY25hKTsKCiMgUmVjYWxjdWxhdGUgTUVzIHdpdGggY29sb3IgbGFiZWxzCk1FczAgPSBtb2R1bGVFaWdlbmdlbmVzKGZhX3dnY25hLCBtb2R1bGVDb2xvcnMpJGVpZ2VuZ2VuZXMKTUVzID0gb3JkZXJNRXMoTUVzMCkKbW9kdWxlVHJhaXRDb3IgPSBjb3IoTUVzLCB0cmFpdHNfZGF0YSwgdXNlID0gInAiKTsKbW9kdWxlVHJhaXRQdmFsdWUgPSBjb3JQdmFsdWVTdHVkZW50KG1vZHVsZVRyYWl0Q29yLCBuU2FtcGxlcyk7CgpvcHRpb25zKHJlcHIucGxvdC53aWR0aD0xNiwgcmVwci5wbG90LmhlaWdodD0xMikKIyBXaWxsIGRpc3BsYXkgY29ycmVsYXRpb25zIGFuZCB0aGVpciBwLXZhbHVlcwp0ZXh0TWF0cml4ID0gIHBhc3RlKHNpZ25pZihtb2R1bGVUcmFpdENvciwgMiksICJcbigiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzaWduaWYobW9kdWxlVHJhaXRQdmFsdWUsIDEpLCAiKSIsIHNlcCA9ICIiKTsKZGltKHRleHRNYXRyaXgpID0gZGltKG1vZHVsZVRyYWl0Q29yKQpwYXIobWFyID0gYyg2LCAxMSwgMSwgMCkpOwojIERpc3BsYXkgdGhlIGNvcnJlbGF0aW9uIHZhbHVlcyB3aXRoaW4gYSBoZWF0bWFwIHBsb3QKbGFiZWxlZEhlYXRtYXAoTWF0cml4ID0gbW9kdWxlVHJhaXRDb3IsCiAgICAgICAgICAgICAgIHhMYWJlbHMgPSBuYW1lcyh0cmFpdHNfZGF0YSksCiAgICAgICAgICAgICAgIHlMYWJlbHMgPSBuYW1lcyhNRXMpLAogICAgICAgICAgICAgICB5U3ltYm9scyA9IG5hbWVzKE1FcyksCiAgICAgICAgICAgICAgIGNvbG9yTGFiZWxzID0gRkFMU0UsCiAgICAgICAgICAgICAgIGNvbG9ycyA9IGJsdWVXaGl0ZVJlZCg1MCksCiAgICAgICAgICAgICAgIHRleHRNYXRyaXggPSB0ZXh0TWF0cml4LAogICAgICAgICAgICAgICBzZXRTdGRNYXJnaW5zID0gRkFMU0UsCiAgICAgICAgICAgICAgIGNleC50ZXh0ID0gMC41LAogICAgICAgICAgICAgICBjZXgubGVnZW5kTGFiZWwgPSAwLjYsCiAgICAgICAgICAgICAgIHpsaW0gPSBjKC0xLDEpLAogICAgICAgICAgICAgICBtYWluID0gcGFzdGUoIk1vZHVsZS10cmFpdCByZWxhdGlvbnNoaXBzIikpCmBgYAoKCkonYWkgdGVudMOpIGRlIGNvbXBhcmVyIGwndXRpbGlzYXRpb24gZHUgVE9NIHNhbnMgcHLDqWRpY3Rpb24gZGUgbW9kdWxlLCByZXByw6lzZW50YW50IGwnZW5zZW1ibGUgZGVzIGfDqG5lcywgYXZlYyBsZSBtb2RUT00gcXVpIG5lIGNvbnRpZW50IHF1J3VuZSBzw6lsZWN0aW9uIGRlIGRldXggbW9kdWxlcyBkJ2ludMOpcsOqdC4KCkonYWkgcmVuY29udHLDqSBkZXMgZGlmZmljdWx0w6lzIGF2ZWMgbCd1dGlsaXNhdGlvbiBkdSBfdGhyZXNob2xkXywgbWVzIGRvbm7DqWVzIG5lIGTDqXBhc3NhbnQgamFtYWlzIGxlIG5pdmVhdSBkZW1hbmTDqSBkZSAwLjUgKHZvaXIgaGlzdG9ncmFtbWVzKSBtYWxncsOpIGwnZXNzYWkgZGUgbm9tYnJldXggcGFyYW3DqHRyZXMsIHB1aXNzYW5jZXMgZXQgbcOpdGhvZGVzLiAKCkonYWkgYWxvcnMgc3VwcG9zw6kgcXVlIGNlIHRocmVzaG9sZCDDqXRhaXQgYmFzw6kgc3VyIGwnX2FkamFjZW5jeV8gZXQgcGFzIGRpcmVjdGVtZW50IGxhIGNvcnLDqWxhdGlvbiwgZXQgZW4gYWkgY2FsY3Vsw6kgdW4gZW4gc3VpdmFudCBsYSBmb3JtdWxlIGRlIGNhbGN1bCBkZSBsYSBtw6l0aG9kZSBfInNpZ25lZCJfIHN1ciB1bmUgY29ycsOpbGF0aW9uIGRlIDAuNSBldCB1biBfcG93ZXJfIGRlIDYsIHNvaXQgZW52aXJvbiAwLjE4LgoKYGBge3IgbGFzdF90b3VjaGVzX3RvX3dnY25hX2FuYWx5c2lzLCBtZXNzYWdlPUZBTFNFfQoKIyBSZWNhbGN1bGF0ZSB0b3BvbG9naWNhbCBvdmVybGFwIGlmIG5lZWRlZApUT00gPSBUT01zaW1pbGFyaXR5RnJvbUV4cHIoZmFfd2djbmEsIHBvd2VyID0gNiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ldHdvcmtUeXBlID0gInNpZ25lZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gMCkKCiMgU2VsZWN0IG1vZHVsZXMKIyBJIHBpY2sgb25lIG1vZHVsZSBzdHJvbmdseSBleHByZXNzZWQgaW4gbm9ybWFsIHNhbXBsZXMgYW5kIG9uZSBpbiBkYXkgNyBzYW1wbGVzCm1vZHVsZXMgPSBjKCJ5ZWxsb3ciLCAid2hpdGUiKTsgIAoKIyBTZWxlY3QgbW9kdWxlIHByb2Jlcwpwcm9iZXMgPSBjb2xuYW1lcyhmYV93Z2NuYSkKaW5Nb2R1bGUgPSBpcy5maW5pdGUobWF0Y2gobW9kdWxlQ29sb3JzLCBtb2R1bGVzKSkKbW9kUHJvYmVzID0gcHJvYmVzW2luTW9kdWxlXQoKIyBTZWxlY3QgdGhlIGNvcnJlc3BvbmRpbmcgVG9wb2xvZ2ljYWwgT3ZlcmxhcAptb2RUT00gPSBUT01baW5Nb2R1bGUsIGluTW9kdWxlXTsKZGltbmFtZXMobW9kVE9NKSA9IGxpc3QobW9kUHJvYmVzLCBtb2RQcm9iZXMpCgojIyMjCiNBZGphY2VuY3kgdmFsdWVzIHZpc3VhbGl6YXRpb24gdG8gZGVjaWRlIG9uIHRocmVzaG9sZApwYXIobWZyb3c9YygxLDIpKQpoaXN0KHVubGlzdChUT00pLCBicmVha3MgPSAxMDAsIGNleC5tYWluID0gMC44KQpoaXN0KHVubGlzdChtb2RUT00pLCBicmVha3MgPSAxMDAsIGNleC5tYWluID0gMC44KQoKIyB0aHJlc2hvbGQgOiAiYWRqYWNlbmN5IHRocmVzaG9sZCBmb3IgaW5jbHVkaW5nIGVkZ2VzIGluIHRoZSBvdXRwdXQuIiAoZnJvbSB0aGUgZG9jKQojIFdpdGggInNpZ25lZCIgdHlwZSBhZGphY2VuY3kgLT4gYWRqYWNlbmN5ID0gKDAuNSAqICgxK2NvcikgKV5wb3dlcgojIGlmIGNvcnJlbGF0aW9uID0gMC41IC0+IHdlIGdldCBhZGphY2VuY3kgPSAwLjE4CgpgYGAKCiMjIyBFeHBvcnRhdGlvbiBkZXMgZG9ubsOpZXMgcG91ciBDeXRvc2NhcGUKCkonZXNzYXllIGRlIHByb2R1aXJlIHVuIHNvdXMgcsOpc2VhdSBhdmVjIGxhIHPDqWxlY3Rpb24gZGUgZGV1eCBtb2R1bGVzIHB1aXMgbGUgcsOpc2VhdSBjb21wbGV0LgoKYGBge3IgZXhwb3J0aW5nX25ldHdvcmtfdGhyZXNob2xkXzAuNX0KCiMgRXhwb3J0IHRoZSBuZXR3b3JrIGludG8gZWRnZSBhbmQgbm9kZSBsaXN0IGZpbGVzIEN5dG9zY2FwZSBjYW4gcmVhZAojIyMgSGVyZSBJIGtlZXAgdGhlIHdob2xlIG5ldHdvcmsKY3l0ID0gZXhwb3J0TmV0d29ya1RvQ3l0b3NjYXBlKFRPTSwKICBlZGdlRmlsZSA9IHBhc3RlKCJyZXN1bHRzL0N5dG9zY2FwZUlucHV0LWVkZ2VzIiwgIi50eHQiLCBzZXA9IiIpLAogIG5vZGVGaWxlID0gcGFzdGUoInJlc3VsdHMvQ3l0b3NjYXBlSW5wdXQtbm9kZXMiLCAiLnR4dCIsIHNlcD0iIiksCiAgd2VpZ2h0ZWQgPSBUUlVFLAogIHRocmVzaG9sZCA9IDAuMTgsICMgSSBtb2RpZmllZCB0aGUgdGhyZXNob2xkIGhlcmUKICBub2RlTmFtZXMgPSBwcm9iZXMsCiAgI25vZGVBdHRyID0gbW9kdWxlQ29sb3JzW2luTW9kdWxlXQogICkKCiMgRXhwb3J0IHRoZSBuZXR3b3JrIGludG8gZWRnZSBhbmQgbm9kZSBsaXN0IGZpbGVzIEN5dG9zY2FwZSBjYW4gcmVhZAojIyMgSGVyZSBJIGV4cG9ydCBvbmx5IHR3byBnZW5lIG1vZHVsZXMgb2YgaW50ZXJlc3QKY3l0ID0gZXhwb3J0TmV0d29ya1RvQ3l0b3NjYXBlKG1vZFRPTSwKICBlZGdlRmlsZSA9IHBhc3RlKCJyZXN1bHRzL0N5dG9zY2FwZUlucHV0LWVkZ2VzLSIsIHBhc3RlKG1vZHVsZXMsIGNvbGxhcHNlPSItIiksICIudHh0Iiwgc2VwPSIiKSwKICBub2RlRmlsZSA9IHBhc3RlKCJyZXN1bHRzL0N5dG9zY2FwZUlucHV0LW5vZGVzLSIsIHBhc3RlKG1vZHVsZXMsIGNvbGxhcHNlPSItIiksICIudHh0Iiwgc2VwPSIiKSwKICB3ZWlnaHRlZCA9IFRSVUUsCiAgdGhyZXNob2xkID0gMC4xOCwgIyBJIG1vZGlmaWVkIHRoZSB0aHJlc2hvbGQgaGVyZQogIG5vZGVOYW1lcyA9IG1vZFByb2JlcywKICBub2RlQXR0ciA9IG1vZHVsZUNvbG9yc1tpbk1vZHVsZV0KICApCgpgYGAKCgojIFZpc3VhbGlzYXRpb24gZHUgcsOpc2VhdSBkYW5zIEN5dG9zY2FwZQoKIyMgQ29uc2lnbmUgOgoKKipDb2xvcmV6IGRhbnMgbGUgcsOpc2VhdSBjaG9pc2kgbGVzIG5vZXVkcyBlbiBmb25jdGlvbiBkZXMgZG9ubsOpZXMgZGUgcHJvdMOpb21pcXVlcyBhdmVjIHVuIGdyYWRpZW50IGRlIGNvdWxldXIgY29ycmVzcG9uZGFudCBhdSBmb2xkLWNoYW5nZSBkZXMgZG9ubsOpZXMgZGUgcHJvdMOpb21pcXVlLioqCgoKIyMgU291cy1yw6lzZWF1ICgyIG1vZHVsZXMpCgpWaXN1YWxpc29ucyBkJ2Fib3JkIGxlIHNvdXMtcsOpc2VhdSBhdmVjIGxlcyBkZXV4IG1vZHVsZXMgc8OpbGVjdGlvbm7DqXMuCgpMZXMgZ8OobmVzIHNlIHPDqXBhcmVudCBlbiBkZXV4IHLDqXNlYXV4LiBKJ2FpIGNvbG9yw6kgbGVzIG5vZGVzIGVuIGZvbmN0aW9uIGRlIGxldXIgYXBwYXJ0ZW5hbmNlIGF1IG1vZHVsZSBfeWVsbG93XyAocGx1cyBleHByaW3DqSBlbiBjb25kaXRpb24gbm9ybWFsZSkgb3UgX3doaXRlXyAocGx1cyBleHByaW3DqSBlbiBjb25kaXRpb24gX2RheTdfKS4gRXRyYW5nZW1lbnQgamUgbidvYnNlcnZlIHBhcyBkZSBzw6lwYXJhdGlvbiDDqXZpZGVudGUgZGVzIGfDqG5lcyBlbiBmb25jdGlvbiBkZSBsZXVyIGFwcGFydGVuYW5jZSBhdXggbW9kdWxlcy4gCgohW3Jlc2VhdV8yX21vZHVsZXNdKHJlc3VsdHMvQ3l0b3NjYXBlSW5wdXQtZWRnZXMteWVsbG93LXdoaXRlLnR4dC5wbmcpCgojIyBSw6lzZWF1IGNvbXBsZXQKCk1haW50ZW5hbnQsIHZpc3VhbGlzb25zIGxlIHLDqXNlYXUgY29tcGxldCBkZXMgZ8OobmVzIHZhcmlhYmxlcyA6CgpEYW5zIGNldHRlIHZlcnNpb24gYnJ1dGUgb24gcmV0cm91dmUgbGEgc8OpcGFyYXRpb24gZW4gZGV1eCByw6lzZWF1eCBpc29sw6lzLiBEZSBwbHVzIG9uIGNvbnN0YXRlIHF1ZSBjZWx1aSBkZSBnYXVjaGUgc2VtYmxlIGF2b2lyIGRldXggcGFydGllcyBtb2lucyBmb3J0ZW1lbnQgY29ycmVsw6llcy4gCgohW3Jlc2VhdV9lbnRpZXJdKHJlc3VsdHMvQ3l0b3NjYXBlSW5wdXRfYWxsLnBuZykKClB1aXMsIGplIG4nYWkgY29uc2VydsOpIHF1ZSBsZXMgZ8OobmVzIHBvdXIgbGVzcXVlbHMgdW5lIGluZm9ybWF0aW9uIHByb3TDqWlxdWUgw6l0YWl0IGRpc3BvbmlibGUuCkVuc3VpdGUsIGonYWkgY29sb3LDqSBsZXMgbm9kZXMgZW4gZm9uY3Rpb24gZHUgbG9nIGZvbGQgY2hhbmdlIGVudHJlIGxlcyBjb25kaXRpb25zIG5vcm1hbGVzIGV0IGpvdXI3LgoKT24gY29uc3RhdGUgaWNpIHVuZSBzw6lwYXJhdGlvbiBmcmFuY2hlIGRlcyBnw6huZXMgcGx1cyBleHByaW3DqXMgZW4gY29uZGl0aW9uIF9kYXk3XyBxdSdlbiBjb25kaXRpb24gX25vcm1hbF8gKGVuIHJvdWdlKSBkZSBjZXV4IG1vaW5zIGV4cHJpbcOpcyBxdSdlbiBjb25kaXRpb24gbm9ybWFsZSAoZW4gYmxldSkuCgpMZSByw6lzZWF1IGRlIGRyb2l0ZSByZWdyb3VwZSBkZXMgZ8OobmVzIHBsdXMgZXhwcmltw6lzIGxvcnMgZGUgbGEgbmVwaHJvcGF0aGllLiBDZWx1aSBkZSBnYXVjaGUgZXN0IGludMOpcmVzc2FudCA7IGlsIGNvbnRpZW50IHVuICJww7RsZSIgZGUgZ8OobmVzIG1vaW5zIGV4cHJpbcOpcyBlbiBjb25kaXRpb24gX2RheTdfIHF1J2VuIF9ub3JtYWxfIChyZWZsw6l0YW50IGNlcnRhaW5lbWVudCBsJ2FjdGl2aXTDqSBub3JtYWxlIGQndW4gcmVpbiBzYWluKSBldCB1biBwZXRpdCAicMO0bGUiIGRlIGfDqG5lcyDDoCBsJ2ludmVyc2UgcGx1cyBleHByaW3DqXMuCgpOb3MgZG9ubsOpZXMgcHJvdMOpb21pcXVlcyBkb25uZW50IHVuZSBjb2jDqXJlbmNlIHN1cHBsw6ltZW50YWlyZSDDoCBjZSByw6lzZWF1IGluZsOpcsOpIGF2ZWMgV0dDTkEuIEF1c3NpLCBqZSBwZW5zZSBxdWUgbCd1dGlsaXNhdGlvbiBkZSBERVNlcTIgcG91ciBsZXMgZG9ubsOpZXMgZGUgc3BlY3Ryb23DqXRyaWUgZGUgbWFzc2Ugbidlc3QgcGV1dC3DqnRyZSBwYXMgaWTDqWFsZSwgbWFpcyBkb25uZSB1biByw6lzdWxhdCBmaW5hbCBnbG9iYWxlbWVudCBjb2jDqXJlbnQgYXZlYyBsZXMgZG9ubsOpZXMgZGUgUk5BLXNlcS4KCiFbcmVzZWF1X2VudGllcl9wcm90XShyZXN1bHRzL0N5dG9zY2FwZUlucHV0LWVkZ2VzLnR4dC5wbmcpCmBgYHtyIHNlc3Npb25faW5mb30KCnNlc3Npb25JbmZvKCkKCmBgYAoK